1 /*
2 * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.font;
27
28 import java.awt.Font;
29 import java.awt.FontFormatException;
30 import java.io.BufferedReader;
31 import java.io.File;
32 import java.io.FileInputStream;
33 import java.io.FilenameFilter;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.security.AccessController;
37 import java.security.PrivilegedAction;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.Hashtable;
42 import java.util.Iterator;
43 import java.util.Locale;
44 import java.util.Map;
45 import java.util.NoSuchElementException;
46 import java.util.StringTokenizer;
47 import java.util.TreeMap;
48 import java.util.Vector;
49 import java.util.concurrent.ConcurrentHashMap;
50
51 import javax.swing.plaf.FontUIResource;
52 import sun.awt.AppContext;
53 import sun.awt.FontConfiguration;
54 import sun.awt.SunToolkit;
55 import sun.java2d.FontSupport;
56 import sun.util.logging.PlatformLogger;
57
58 /**
59 * The base implementation of the {@link FontManager} interface. It implements
60 * the platform independent, shared parts of OpenJDK's FontManager
61 * implementations. The platform specific parts are declared as abstract
62 * methods that have to be implemented by specific implementations.
63 */
64 public abstract class SunFontManager implements FontSupport, FontManagerForSGE {
65
66 private static class TTFilter implements FilenameFilter {
67 public boolean accept(File dir,String name) {
68 /* all conveniently have the same suffix length */
69 int offset = name.length()-4;
70 if (offset <= 0) { /* must be at least A.ttf */
71 return false;
72 } else {
73 return(name.startsWith(".ttf", offset) ||
74 name.startsWith(".TTF", offset) ||
75 name.startsWith(".ttc", offset) ||
76 name.startsWith(".TTC", offset) ||
77 name.startsWith(".otf", offset) ||
78 name.startsWith(".OTF", offset));
79 }
80 }
81 }
82
83 private static class T1Filter implements FilenameFilter {
84 public boolean accept(File dir,String name) {
85 if (noType1Font) {
86 return false;
87 }
88 /* all conveniently have the same suffix length */
89 int offset = name.length()-4;
90 if (offset <= 0) { /* must be at least A.pfa */
91 return false;
92 } else {
93 return(name.startsWith(".pfa", offset) ||
94 name.startsWith(".pfb", offset) ||
95 name.startsWith(".PFA", offset) ||
96 name.startsWith(".PFB", offset));
97 }
98 }
99 }
100
101 private static class TTorT1Filter implements FilenameFilter {
102 public boolean accept(File dir, String name) {
103
104 /* all conveniently have the same suffix length */
105 int offset = name.length()-4;
106 if (offset <= 0) { /* must be at least A.ttf or A.pfa */
107 return false;
108 } else {
109 boolean isTT =
110 name.startsWith(".ttf", offset) ||
111 name.startsWith(".TTF", offset) ||
112 name.startsWith(".ttc", offset) ||
113 name.startsWith(".TTC", offset) ||
114 name.startsWith(".otf", offset) ||
115 name.startsWith(".OTF", offset);
116 if (isTT) {
117 return true;
118 } else if (noType1Font) {
119 return false;
120 } else {
121 return(name.startsWith(".pfa", offset) ||
122 name.startsWith(".pfb", offset) ||
123 name.startsWith(".PFA", offset) ||
124 name.startsWith(".PFB", offset));
125 }
126 }
127 }
128 }
129
130 public static final int FONTFORMAT_NONE = -1;
131 public static final int FONTFORMAT_TRUETYPE = 0;
132 public static final int FONTFORMAT_TYPE1 = 1;
133 public static final int FONTFORMAT_T2K = 2;
134 public static final int FONTFORMAT_TTC = 3;
135 public static final int FONTFORMAT_COMPOSITE = 4;
136 public static final int FONTFORMAT_NATIVE = 5;
137
138 /* Pool of 20 font file channels chosen because some UTF-8 locale
139 * composite fonts can use up to 16 platform fonts (including the
140 * Lucida fall back). This should prevent channel thrashing when
141 * dealing with one of these fonts.
142 * The pool array stores the fonts, rather than directly referencing
143 * the channels, as the font needs to do the open/close work.
144 */
145 private static final int CHANNELPOOLSIZE = 20;
146 private int lastPoolIndex = 0;
147 private FileFont fontFileCache[] = new FileFont[CHANNELPOOLSIZE];
148
149 /* Need to implement a simple linked list scheme for fast
150 * traversal and lookup.
151 * Also want to "fast path" dialog so there's minimal overhead.
152 */
153 /* There are at exactly 20 composite fonts: 5 faces (but some are not
154 * usually different), in 4 styles. The array may be auto-expanded
155 * later if more are needed, eg for user-defined composites or locale
156 * variants.
157 */
158 private int maxCompFont = 0;
159 private CompositeFont [] compFonts = new CompositeFont[20];
160 private ConcurrentHashMap<String, CompositeFont>
161 compositeFonts = new ConcurrentHashMap<String, CompositeFont>();
162 private ConcurrentHashMap<String, PhysicalFont>
163 physicalFonts = new ConcurrentHashMap<String, PhysicalFont>();
164 private ConcurrentHashMap<String, PhysicalFont>
165 registeredFonts = new ConcurrentHashMap<String, PhysicalFont>();
166
167 /* given a full name find the Font. Remind: there's duplication
168 * here in that this contains the content of compositeFonts +
169 * physicalFonts.
170 */
171 private ConcurrentHashMap<String, Font2D>
172 fullNameToFont = new ConcurrentHashMap<String, Font2D>();
173
174 /* TrueType fonts have localised names. Support searching all
175 * of these before giving up on a name.
176 */
177 private HashMap<String, TrueTypeFont> localeFullNamesToFont;
178
179 private PhysicalFont defaultPhysicalFont;
180
181 static boolean longAddresses;
182 private boolean loaded1dot0Fonts = false;
183 boolean loadedAllFonts = false;
184 boolean loadedAllFontFiles = false;
185 HashMap<String,String> jreFontMap;
186 HashSet<String> jreLucidaFontFiles;
187 String[] jreOtherFontFiles;
188 boolean noOtherJREFontFiles = false; // initial assumption.
189
190 public static final String lucidaFontName = "Lucida Sans Regular";
191 public static String jreLibDirName;
192 public static String jreFontDirName;
193 private static HashSet<String> missingFontFiles = null;
194 private String defaultFontName;
195 private String defaultFontFileName;
196 protected HashSet registeredFontFiles = new HashSet();
197
198 private ArrayList badFonts;
199 /* fontPath is the location of all fonts on the system, excluding the
200 * JRE's own font directory but including any path specified using the
201 * sun.java2d.fontpath property. Together with that property, it is
202 * initialised by the getPlatformFontPath() method
203 * This call must be followed by a call to registerFontDirs(fontPath)
204 * once any extra debugging path has been appended.
205 */
206 protected String fontPath;
207 private FontConfiguration fontConfig;
208 /* discoveredAllFonts is set to true when all fonts on the font path are
209 * discovered. This usually also implies opening, validating and
210 * registering, but an implementation may be optimized to avold this.
211 * So see also "loadedAllFontFiles"
212 */
213 private boolean discoveredAllFonts = false;
214
215 /* No need to keep consing up new instances - reuse a singleton.
216 * The trade-off is that these objects don't get GC'd.
217 */
218 private static final FilenameFilter ttFilter = new TTFilter();
219 private static final FilenameFilter t1Filter = new T1Filter();
220
221 private Font[] allFonts;
222 private String[] allFamilies; // cache for default locale only
223 private Locale lastDefaultLocale;
224
225 public static boolean noType1Font;
226
227 /* Used to indicate required return type from toArray(..); */
228 private static String[] STR_ARRAY = new String[0];
229
230 /**
231 * Deprecated, unsupported hack - actually invokes a bug!
232 * Left in for a customer, don't remove.
233 */
234 private boolean usePlatformFontMetrics = false;
235
236 /**
237 * Returns the global SunFontManager instance. This is similar to
238 * {@link FontManagerFactory#getInstance()} but it returns a
239 * SunFontManager instance instead. This is only used in internal classes
240 * where we can safely assume that a SunFontManager is to be used.
241 *
242 * @return the global SunFontManager instance
243 */
244 public static SunFontManager getInstance() {
245 FontManager fm = FontManagerFactory.getInstance();
246 return (SunFontManager) fm;
247 }
248
249 public FilenameFilter getTrueTypeFilter() {
250 return ttFilter;
251 }
252
253 public FilenameFilter getType1Filter() {
254 return t1Filter;
255 }
256
257 @Override
258 public boolean usingPerAppContextComposites() {
259 return _usingPerAppContextComposites;
260 }
261
262 private void initJREFontMap() {
263
264 /* Key is familyname+style value as an int.
265 * Value is filename containing the font.
266 * If no mapping exists, it means there is no font file for the style
267 * If the mapping exists but the file doesn't exist in the deferred
268 * list then it means its not installed.
269 * This looks like a lot of code and strings but if it saves even
270 * a single file being opened at JRE start-up there's a big payoff.
271 * Lucida Sans is probably the only important case as the others
272 * are rarely used. Consider removing the other mappings if there's
273 * no evidence they are useful in practice.
274 */
275 jreFontMap = new HashMap<String,String>();
276 jreLucidaFontFiles = new HashSet<String>();
277 if (isOpenJDK()) {
278 return;
279 }
280 /* Lucida Sans Family */
281 jreFontMap.put("lucida sans0", "LucidaSansRegular.ttf");
282 jreFontMap.put("lucida sans1", "LucidaSansDemiBold.ttf");
283 /* Lucida Sans full names (map Bold and DemiBold to same file) */
284 jreFontMap.put("lucida sans regular0", "LucidaSansRegular.ttf");
285 jreFontMap.put("lucida sans regular1", "LucidaSansDemiBold.ttf");
286 jreFontMap.put("lucida sans bold1", "LucidaSansDemiBold.ttf");
287 jreFontMap.put("lucida sans demibold1", "LucidaSansDemiBold.ttf");
288
289 /* Lucida Sans Typewriter Family */
290 jreFontMap.put("lucida sans typewriter0",
291 "LucidaTypewriterRegular.ttf");
292 jreFontMap.put("lucida sans typewriter1", "LucidaTypewriterBold.ttf");
293 /* Typewriter full names (map Bold and DemiBold to same file) */
294 jreFontMap.put("lucida sans typewriter regular0",
295 "LucidaTypewriter.ttf");
296 jreFontMap.put("lucida sans typewriter regular1",
297 "LucidaTypewriterBold.ttf");
298 jreFontMap.put("lucida sans typewriter bold1",
299 "LucidaTypewriterBold.ttf");
300 jreFontMap.put("lucida sans typewriter demibold1",
301 "LucidaTypewriterBold.ttf");
302
303 /* Lucida Bright Family */
304 jreFontMap.put("lucida bright0", "LucidaBrightRegular.ttf");
305 jreFontMap.put("lucida bright1", "LucidaBrightDemiBold.ttf");
306 jreFontMap.put("lucida bright2", "LucidaBrightItalic.ttf");
307 jreFontMap.put("lucida bright3", "LucidaBrightDemiItalic.ttf");
308 /* Lucida Bright full names (map Bold and DemiBold to same file) */
309 jreFontMap.put("lucida bright regular0", "LucidaBrightRegular.ttf");
310 jreFontMap.put("lucida bright regular1", "LucidaBrightDemiBold.ttf");
311 jreFontMap.put("lucida bright regular2", "LucidaBrightItalic.ttf");
312 jreFontMap.put("lucida bright regular3", "LucidaBrightDemiItalic.ttf");
313 jreFontMap.put("lucida bright bold1", "LucidaBrightDemiBold.ttf");
314 jreFontMap.put("lucida bright bold3", "LucidaBrightDemiItalic.ttf");
315 jreFontMap.put("lucida bright demibold1", "LucidaBrightDemiBold.ttf");
316 jreFontMap.put("lucida bright demibold3","LucidaBrightDemiItalic.ttf");
317 jreFontMap.put("lucida bright italic2", "LucidaBrightItalic.ttf");
318 jreFontMap.put("lucida bright italic3", "LucidaBrightDemiItalic.ttf");
319 jreFontMap.put("lucida bright bold italic3",
320 "LucidaBrightDemiItalic.ttf");
321 jreFontMap.put("lucida bright demibold italic3",
322 "LucidaBrightDemiItalic.ttf");
323 for (String ffile : jreFontMap.values()) {
324 jreLucidaFontFiles.add(ffile);
325 }
326 }
327
328 static {
329
330 java.security.AccessController.doPrivileged(
331 new java.security.PrivilegedAction() {
332
333 public Object run() {
334 FontManagerNativeLibrary.load();
335
336 // JNI throws an exception if a class/method/field is not found,
337 // so there's no need to do anything explicit here.
338 initIDs();
339
340 switch (StrikeCache.nativeAddressSize) {
341 case 8: longAddresses = true; break;
342 case 4: longAddresses = false; break;
343 default: throw new RuntimeException("Unexpected address size");
344 }
345
346 noType1Font =
347 "true".equals(System.getProperty("sun.java2d.noType1Font"));
348 jreLibDirName =
349 System.getProperty("java.home","") + File.separator + "lib";
350 jreFontDirName = jreLibDirName + File.separator + "fonts";
351 File lucidaFile =
352 new File(jreFontDirName + File.separator + FontUtilities.LUCIDA_FILE_NAME);
353
354 return null;
355 }
356 });
357 }
358
359 public TrueTypeFont getEUDCFont() {
360 // Overridden in Windows.
361 return null;
362 }
363
364 /* Initialise ptrs used by JNI methods */
365 private static native void initIDs();
366
367 @SuppressWarnings("unchecked")
368 protected SunFontManager() {
369
370 initJREFontMap();
371 java.security.AccessController.doPrivileged(
372 new java.security.PrivilegedAction() {
373 public Object run() {
374 File badFontFile =
375 new File(jreFontDirName + File.separator +
376 "badfonts.txt");
377 if (badFontFile.exists()) {
378 FileInputStream fis = null;
379 try {
380 badFonts = new ArrayList();
381 fis = new FileInputStream(badFontFile);
382 InputStreamReader isr = new InputStreamReader(fis);
383 BufferedReader br = new BufferedReader(isr);
384 while (true) {
385 String name = br.readLine();
386 if (name == null) {
387 break;
388 } else {
389 if (FontUtilities.debugFonts()) {
390 FontUtilities.getLogger().warning("read bad font: " +
391 name);
392 }
393 badFonts.add(name);
394 }
395 }
396 } catch (IOException e) {
397 try {
398 if (fis != null) {
399 fis.close();
400 }
401 } catch (IOException ioe) {
402 }
403 }
404 }
405
406 /* Here we get the fonts in jre/lib/fonts and register
407 * them so they are always available and preferred over
408 * other fonts. This needs to be registered before the
409 * composite fonts as otherwise some native font that
410 * corresponds may be found as we don't have a way to
411 * handle two fonts of the same name, so the JRE one
412 * must be the first one registered. Pass "true" to
413 * registerFonts method as on-screen these JRE fonts
414 * always go through the T2K rasteriser.
415 */
416 if (FontUtilities.isLinux) {
417 /* Linux font configuration uses these fonts */
418 registerFontDir(jreFontDirName);
419 }
420 registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK,
421 true, false);
422
423 /* Create the font configuration and get any font path
424 * that might be specified.
425 */
426 fontConfig = createFontConfiguration();
427 if (isOpenJDK()) {
428 String[] fontInfo = getDefaultPlatformFont();
429 defaultFontName = fontInfo[0];
430 defaultFontFileName = fontInfo[1];
431 }
432
433 String extraFontPath = fontConfig.getExtraFontPath();
434
435 /* In prior releases the debugging font path replaced
436 * all normally located font directories except for the
437 * JRE fonts dir. This directory is still always located
438 * and placed at the head of the path but as an
439 * augmentation to the previous behaviour the
440 * changes below allow you to additionally append to
441 * the font path by starting with append: or prepend by
442 * starting with a prepend: sign. Eg: to append
443 * -Dsun.java2d.fontpath=append:/usr/local/myfonts
444 * and to prepend
445 * -Dsun.java2d.fontpath=prepend:/usr/local/myfonts Disp
446 *
447 * If there is an appendedfontpath it in the font
448 * configuration it is used instead of searching the
449 * system for dirs.
450 * The behaviour of append and prepend is then similar
451 * to the normal case. ie it goes after what
452 * you prepend and * before what you append. If the
453 * sun.java2d.fontpath property is used, but it
454 * neither the append or prepend syntaxes is used then
455 * as except for the JRE dir the path is replaced and it
456 * is up to you to make sure that all the right
457 * directories are located. This is platform and
458 * locale-specific so its almost impossible to get
459 * right, so it should be used with caution.
460 */
461 boolean prependToPath = false;
462 boolean appendToPath = false;
463 String dbgFontPath =
464 System.getProperty("sun.java2d.fontpath");
465
466 if (dbgFontPath != null) {
467 if (dbgFontPath.startsWith("prepend:")) {
468 prependToPath = true;
469 dbgFontPath =
470 dbgFontPath.substring("prepend:".length());
471 } else if (dbgFontPath.startsWith("append:")) {
472 appendToPath = true;
473 dbgFontPath =
474 dbgFontPath.substring("append:".length());
475 }
476 }
477
478 if (FontUtilities.debugFonts()) {
479 PlatformLogger logger = FontUtilities.getLogger();
480 logger.info("JRE font directory: " + jreFontDirName);
481 logger.info("Extra font path: " + extraFontPath);
482 logger.info("Debug font path: " + dbgFontPath);
483 }
484
485 if (dbgFontPath != null) {
486 /* In debugging mode we register all the paths
487 * Caution: this is a very expensive call on Solaris:-
488 */
489 fontPath = getPlatformFontPath(noType1Font);
490
491 if (extraFontPath != null) {
492 fontPath =
493 extraFontPath + File.pathSeparator + fontPath;
494 }
495 if (appendToPath) {
496 fontPath =
497 fontPath + File.pathSeparator + dbgFontPath;
498 } else if (prependToPath) {
499 fontPath =
500 dbgFontPath + File.pathSeparator + fontPath;
501 } else {
502 fontPath = dbgFontPath;
503 }
504 registerFontDirs(fontPath);
505 } else if (extraFontPath != null) {
506 /* If the font configuration contains an
507 * "appendedfontpath" entry, it is interpreted as a
508 * set of locations that should always be registered.
509 * It may be additional to locations normally found
510 * for that place, or it may be locations that need
511 * to have all their paths registered to locate all
512 * the needed platform names.
513 * This is typically when the same .TTF file is
514 * referenced from multiple font.dir files and all
515 * of these must be read to find all the native
516 * (XLFD) names for the font, so that X11 font APIs
517 * can be used for as many code points as possible.
518 */
519 registerFontDirs(extraFontPath);
520 }
521
522 /* On Solaris, we need to register the Japanese TrueType
523 * directory so that we can find the corresponding
524 * bitmap fonts. This could be done by listing the
525 * directory in the font configuration file, but we
526 * don't want to confuse users with this quirk. There
527 * are no bitmap fonts for other writing systems that
528 * correspond to TrueType fonts and have matching XLFDs.
529 * We need to register the bitmap fonts only in
530 * environments where they're on the X font path, i.e.,
531 * in the Japanese locale. Note that if the X Toolkit
532 * is in use the font path isn't set up by JDK, but
533 * users of a JA locale should have it
534 * set up already by their login environment.
535 */
536 if (FontUtilities.isSolaris && Locale.JAPAN.equals(Locale.getDefault())) {
537 registerFontDir("/usr/openwin/lib/locale/ja/X11/fonts/TT");
538 }
539
540 initCompositeFonts(fontConfig, null);
541
542 return null;
543 }
544 });
545
546 boolean platformFont = AccessController.doPrivileged(
547 new PrivilegedAction<Boolean>() {
548 public Boolean run() {
549 String prop =
550 System.getProperty("java2d.font.usePlatformFont");
551 String env = System.getenv("JAVA2D_USEPLATFORMFONT");
552 return "true".equals(prop) || env != null;
553 }
554 });
555
556 if (platformFont) {
557 usePlatformFontMetrics = true;
558 System.out.println("Enabling platform font metrics for win32. This is an unsupported option.");
559 System.out.println("This yields incorrect composite font metrics as reported by 1.1.x releases.");
560 System.out.println("It is appropriate only for use by applications which do not use any Java 2");
561 System.out.println("functionality. This property will be removed in a later release.");
562 }
563 }
564
565 /**
566 * This method is provided for internal and exclusive use by Swing.
567 *
568 * @param font representing a physical font.
569 * @return true if the underlying font is a TrueType or OpenType font
570 * that claims to support the Microsoft Windows encoding corresponding to
571 * the default file.encoding property of this JRE instance.
572 * This narrow value is useful for Swing to decide if the font is useful
573 * for the the Windows Look and Feel, or, if a composite font should be
574 * used instead.
575 * The information used to make the decision is obtained from
576 * the ulCodePageRange fields in the font.
577 * A caller can use isLogicalFont(Font) in this class before calling
578 * this method and would not need to call this method if that
579 * returns true.
580 */
581 // static boolean fontSupportsDefaultEncoding(Font font) {
582 // String encoding =
583 // (String) java.security.AccessController.doPrivileged(
584 // new sun.security.action.GetPropertyAction("file.encoding"));
585
586 // if (encoding == null || font == null) {
587 // return false;
588 // }
589
590 // encoding = encoding.toLowerCase(Locale.ENGLISH);
591
592 // return FontManager.fontSupportsEncoding(font, encoding);
593 // }
594
595 public Font2DHandle getNewComposite(String family, int style,
596 Font2DHandle handle) {
597
598 if (!(handle.font2D instanceof CompositeFont)) {
599 return handle;
600 }
601
602 CompositeFont oldComp = (CompositeFont)handle.font2D;
603 PhysicalFont oldFont = oldComp.getSlotFont(0);
604
605 if (family == null) {
606 family = oldFont.getFamilyName(null);
607 }
608 if (style == -1) {
609 style = oldComp.getStyle();
610 }
611
612 Font2D newFont = findFont2D(family, style, NO_FALLBACK);
613 if (!(newFont instanceof PhysicalFont)) {
614 newFont = oldFont;
615 }
616 PhysicalFont physicalFont = (PhysicalFont)newFont;
617 CompositeFont dialog2D =
618 (CompositeFont)findFont2D("dialog", style, NO_FALLBACK);
619 if (dialog2D == null) { /* shouldn't happen */
620 return handle;
621 }
622 CompositeFont compFont = new CompositeFont(physicalFont, dialog2D);
623 Font2DHandle newHandle = new Font2DHandle(compFont);
624 return newHandle;
625 }
626
627 protected void registerCompositeFont(String compositeName,
628 String[] componentFileNames,
629 String[] componentNames,
630 int numMetricsSlots,
631 int[] exclusionRanges,
632 int[] exclusionMaxIndex,
633 boolean defer) {
634
635 CompositeFont cf = new CompositeFont(compositeName,
636 componentFileNames,
637 componentNames,
638 numMetricsSlots,
639 exclusionRanges,
640 exclusionMaxIndex, defer, this);
641 addCompositeToFontList(cf, Font2D.FONT_CONFIG_RANK);
642 synchronized (compFonts) {
643 compFonts[maxCompFont++] = cf;
644 }
645 }
646
647 /* This variant is used only when the application specifies
648 * a variant of composite fonts which prefers locale specific or
649 * proportional fonts.
650 */
651 protected static void registerCompositeFont(String compositeName,
652 String[] componentFileNames,
653 String[] componentNames,
654 int numMetricsSlots,
655 int[] exclusionRanges,
656 int[] exclusionMaxIndex,
657 boolean defer,
658 ConcurrentHashMap<String, Font2D>
659 altNameCache) {
660
661 CompositeFont cf = new CompositeFont(compositeName,
662 componentFileNames,
663 componentNames,
664 numMetricsSlots,
665 exclusionRanges,
666 exclusionMaxIndex, defer,
667 SunFontManager.getInstance());
668
669 /* if the cache has an existing composite for this case, make
670 * its handle point to this new font.
671 * This ensures that when the altNameCache that is passed in
672 * is the global mapNameCache - ie we are running as an application -
673 * that any statically created java.awt.Font instances which already
674 * have a Font2D instance will have that re-directed to the new Font
675 * on subsequent uses. This is particularly important for "the"
676 * default font instance, or similar cases where a UI toolkit (eg
677 * Swing) has cached a java.awt.Font. Note that if Swing is using
678 * a custom composite APIs which update the standard composites have
679 * no effect - this is typically the case only when using the Windows
680 * L&F where these APIs would conflict with that L&F anyway.
681 */
682 Font2D oldFont = (Font2D)
683 altNameCache.get(compositeName.toLowerCase(Locale.ENGLISH));
684 if (oldFont instanceof CompositeFont) {
685 oldFont.handle.font2D = cf;
686 }
687 altNameCache.put(compositeName.toLowerCase(Locale.ENGLISH), cf);
688 }
689
690 private void addCompositeToFontList(CompositeFont f, int rank) {
691
692 if (FontUtilities.isLogging()) {
693 FontUtilities.getLogger().info("Add to Family "+ f.familyName +
694 ", Font " + f.fullName + " rank="+rank);
695 }
696 f.setRank(rank);
697 compositeFonts.put(f.fullName, f);
698 fullNameToFont.put(f.fullName.toLowerCase(Locale.ENGLISH), f);
699
700 FontFamily family = FontFamily.getFamily(f.familyName);
701 if (family == null) {
702 family = new FontFamily(f.familyName, true, rank);
703 }
704 family.setFont(f, f.style);
705 }
706
707 /*
708 * Systems may have fonts with the same name.
709 * We want to register only one of such fonts (at least until
710 * such time as there might be APIs which can accommodate > 1).
711 * Rank is 1) font configuration fonts, 2) JRE fonts, 3) OT/TT fonts,
712 * 4) Type1 fonts, 5) native fonts.
713 *
714 * If the new font has the same name as the old font, the higher
715 * ranked font gets added, replacing the lower ranked one.
716 * If the fonts are of equal rank, then make a special case of
717 * font configuration rank fonts, which are on closer inspection,
718 * OT/TT fonts such that the larger font is registered. This is
719 * a heuristic since a font may be "larger" in the sense of more
720 * code points, or be a larger "file" because it has more bitmaps.
721 * So it is possible that using filesize may lead to less glyphs, and
722 * using glyphs may lead to lower quality display. Probably number
723 * of glyphs is the ideal, but filesize is information we already
724 * have and is good enough for the known cases.
725 * Also don't want to register fonts that match JRE font families
726 * but are coming from a source other than the JRE.
727 * This will ensure that we will algorithmically style the JRE
728 * plain font and get the same set of glyphs for all styles.
729 *
730 * Note that this method returns a value
731 * if it returns the same object as its argument that means this
732 * font was newly registered.
733 * If it returns a different object it means this font already exists,
734 * and you should use that one.
735 * If it returns null means this font was not registered and none
736 * in that name is registered. The caller must find a substitute
737 */
738 private PhysicalFont addToFontList(PhysicalFont f, int rank) {
739
740 String fontName = f.fullName;
741 String familyName = f.familyName;
742 if (fontName == null || "".equals(fontName)) {
743 return null;
744 }
745 if (compositeFonts.containsKey(fontName)) {
746 /* Don't register any font that has the same name as a composite */
747 return null;
748 }
749 f.setRank(rank);
750 if (!physicalFonts.containsKey(fontName)) {
751 if (FontUtilities.isLogging()) {
752 FontUtilities.getLogger().info("Add to Family "+familyName +
753 ", Font " + fontName + " rank="+rank);
754 }
755 physicalFonts.put(fontName, f);
756 FontFamily family = FontFamily.getFamily(familyName);
757 if (family == null) {
758 family = new FontFamily(familyName, false, rank);
759 family.setFont(f, f.style);
760 } else if (family.getRank() >= rank) {
761 family.setFont(f, f.style);
762 }
763 fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH), f);
764 return f;
765 } else {
766 PhysicalFont newFont = f;
767 PhysicalFont oldFont = physicalFonts.get(fontName);
768 if (oldFont == null) {
769 return null;
770 }
771 /* If the new font is of an equal or higher rank, it is a
772 * candidate to replace the current one, subject to further tests.
773 */
774 if (oldFont.getRank() >= rank) {
775
776 /* All fonts initialise their mapper when first
777 * used. If the mapper is non-null then this font
778 * has been accessed at least once. In that case
779 * do not replace it. This may be overly stringent,
780 * but its probably better not to replace a font that
781 * someone is already using without a compelling reason.
782 * Additionally the primary case where it is known
783 * this behaviour is important is in certain composite
784 * fonts, and since all the components of a given
785 * composite are usually initialised together this
786 * is unlikely. For this to be a problem, there would
787 * have to be a case where two different composites used
788 * different versions of the same-named font, and they
789 * were initialised and used at separate times.
790 * In that case we continue on and allow the new font to
791 * be installed, but replaceFont will continue to allow
792 * the original font to be used in Composite fonts.
793 */
794 if (oldFont.mapper != null && rank > Font2D.FONT_CONFIG_RANK) {
795 return oldFont;
796 }
797
798 /* Normally we require a higher rank to replace a font,
799 * but as a special case, if the two fonts are the same rank,
800 * and are instances of TrueTypeFont we want the
801 * more complete (larger) one.
802 */
803 if (oldFont.getRank() == rank) {
804 if (oldFont instanceof TrueTypeFont &&
805 newFont instanceof TrueTypeFont) {
806 TrueTypeFont oldTTFont = (TrueTypeFont)oldFont;
807 TrueTypeFont newTTFont = (TrueTypeFont)newFont;
808 if (oldTTFont.fileSize >= newTTFont.fileSize) {
809 return oldFont;
810 }
811 } else {
812 return oldFont;
813 }
814 }
815 /* Don't replace ever JRE fonts.
816 * This test is in case a font configuration references
817 * a Lucida font, which has been mapped to a Lucida
818 * from the host O/S. The assumption here is that any
819 * such font configuration file is probably incorrect, or
820 * the host O/S version is for the use of AWT.
821 * In other words if we reach here, there's a possible
822 * problem with our choice of font configuration fonts.
823 */
824 if (oldFont.platName.startsWith(jreFontDirName)) {
825 if (FontUtilities.isLogging()) {
826 FontUtilities.getLogger()
827 .warning("Unexpected attempt to replace a JRE " +
828 " font " + fontName + " from " +
829 oldFont.platName +
830 " with " + newFont.platName);
831 }
832 return oldFont;
833 }
834
835 if (FontUtilities.isLogging()) {
836 FontUtilities.getLogger()
837 .info("Replace in Family " + familyName +
838 ",Font " + fontName + " new rank="+rank +
839 " from " + oldFont.platName +
840 " with " + newFont.platName);
841 }
842 replaceFont(oldFont, newFont);
843 physicalFonts.put(fontName, newFont);
844 fullNameToFont.put(fontName.toLowerCase(Locale.ENGLISH),
845 newFont);
846
847 FontFamily family = FontFamily.getFamily(familyName);
848 if (family == null) {
849 family = new FontFamily(familyName, false, rank);
850 family.setFont(newFont, newFont.style);
851 } else if (family.getRank() >= rank) {
852 family.setFont(newFont, newFont.style);
853 }
854 return newFont;
855 } else {
856 return oldFont;
857 }
858 }
859 }
860
861 public Font2D[] getRegisteredFonts() {
862 PhysicalFont[] physFonts = getPhysicalFonts();
863 int mcf = maxCompFont; /* for MT-safety */
864 Font2D[] regFonts = new Font2D[physFonts.length+mcf];
865 System.arraycopy(compFonts, 0, regFonts, 0, mcf);
866 System.arraycopy(physFonts, 0, regFonts, mcf, physFonts.length);
867 return regFonts;
868 }
869
870 protected PhysicalFont[] getPhysicalFonts() {
871 return physicalFonts.values().toArray(new PhysicalFont[0]);
872 }
873
874
875 /* The class FontRegistrationInfo is used when a client says not
876 * to register a font immediately. This mechanism is used to defer
877 * initialisation of all the components of composite fonts at JRE
878 * start-up. The CompositeFont class is "aware" of this and when it
879 * is first used it asks for the registration of its components.
880 * Also in the event that any physical font is requested the
881 * deferred fonts are initialised before triggering a search of the
882 * system.
883 * Two maps are used. One to track the deferred fonts. The
884 * other to track the fonts that have been initialised through this
885 * mechanism.
886 */
887
888 private static final class FontRegistrationInfo {
889
890 String fontFilePath;
891 String[] nativeNames;
892 int fontFormat;
893 boolean javaRasterizer;
894 int fontRank;
895
896 FontRegistrationInfo(String fontPath, String[] names, int format,
897 boolean useJavaRasterizer, int rank) {
898 this.fontFilePath = fontPath;
899 this.nativeNames = names;
900 this.fontFormat = format;
901 this.javaRasterizer = useJavaRasterizer;
902 this.fontRank = rank;
903 }
904 }
905
906 private final ConcurrentHashMap<String, FontRegistrationInfo>
907 deferredFontFiles =
908 new ConcurrentHashMap<String, FontRegistrationInfo>();
909 private final ConcurrentHashMap<String, Font2DHandle>
910 initialisedFonts = new ConcurrentHashMap<String, Font2DHandle>();
911
912 /* Remind: possibly enhance initialiseDeferredFonts() to be
913 * optionally given a name and a style and it could stop when it
914 * finds that font - but this would be a problem if two of the
915 * fonts reference the same font face name (cf the Solaris
916 * euro fonts).
917 */
918 protected synchronized void initialiseDeferredFonts() {
919 for (String fileName : deferredFontFiles.keySet()) {
920 initialiseDeferredFont(fileName);
921 }
922 }
923
924 protected synchronized void registerDeferredJREFonts(String jreDir) {
925 for (FontRegistrationInfo info : deferredFontFiles.values()) {
926 if (info.fontFilePath != null &&
927 info.fontFilePath.startsWith(jreDir)) {
928 initialiseDeferredFont(info.fontFilePath);
929 }
930 }
931 }
932
933 public boolean isDeferredFont(String fileName) {
934 return deferredFontFiles.containsKey(fileName);
935 }
936
937 /* We keep a map of the files which contain the Lucida fonts so we
938 * don't need to search for them.
939 * But since we know what fonts these files contain, we can also avoid
940 * opening them to look for a font name we don't recognise - see
941 * findDeferredFont().
942 * For typical cases where the font isn't a JRE one the overhead is
943 * this method call, HashMap.get() and null reference test, then
944 * a boolean test of noOtherJREFontFiles.
945 */
946 public
947 /*private*/ PhysicalFont findJREDeferredFont(String name, int style) {
948
949 PhysicalFont physicalFont;
950 String nameAndStyle = name.toLowerCase(Locale.ENGLISH) + style;
951 String fileName = jreFontMap.get(nameAndStyle);
952 if (fileName != null) {
953 fileName = jreFontDirName + File.separator + fileName;
954 if (deferredFontFiles.get(fileName) != null) {
955 physicalFont = initialiseDeferredFont(fileName);
956 if (physicalFont != null &&
957 (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
958 physicalFont.getFamilyName(null).equalsIgnoreCase(name))
959 && physicalFont.style == style) {
960 return physicalFont;
961 }
962 }
963 }
964
965 /* Iterate over the deferred font files looking for any in the
966 * jre directory that we didn't recognise, open each of these.
967 * In almost all installations this will quickly fall through
968 * because only the Lucidas will be present and jreOtherFontFiles
969 * will be empty.
970 * noOtherJREFontFiles is used so we can skip this block as soon
971 * as its determined that its not needed - almost always after the
972 * very first time through.
973 */
974 if (noOtherJREFontFiles) {
975 return null;
976 }
977 synchronized (jreLucidaFontFiles) {
978 if (jreOtherFontFiles == null) {
979 HashSet<String> otherFontFiles = new HashSet<String>();
980 for (String deferredFile : deferredFontFiles.keySet()) {
981 File file = new File(deferredFile);
982 String dir = file.getParent();
983 String fname = file.getName();
984 /* skip names which aren't absolute, aren't in the JRE
985 * directory, or are known Lucida fonts.
986 */
987 if (dir == null ||
988 !dir.equals(jreFontDirName) ||
989 jreLucidaFontFiles.contains(fname)) {
990 continue;
991 }
992 otherFontFiles.add(deferredFile);
993 }
994 jreOtherFontFiles = otherFontFiles.toArray(STR_ARRAY);
995 if (jreOtherFontFiles.length == 0) {
996 noOtherJREFontFiles = true;
997 }
998 }
999
1000 for (int i=0; i<jreOtherFontFiles.length;i++) {
1001 fileName = jreOtherFontFiles[i];
1002 if (fileName == null) {
1003 continue;
1004 }
1005 jreOtherFontFiles[i] = null;
1006 physicalFont = initialiseDeferredFont(fileName);
1007 if (physicalFont != null &&
1008 (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
1009 physicalFont.getFamilyName(null).equalsIgnoreCase(name))
1010 && physicalFont.style == style) {
1011 return physicalFont;
1012 }
1013 }
1014 }
1015
1016 return null;
1017 }
1018
1019 /* This skips JRE installed fonts. */
1020 private PhysicalFont findOtherDeferredFont(String name, int style) {
1021 for (String fileName : deferredFontFiles.keySet()) {
1022 File file = new File(fileName);
1023 String dir = file.getParent();
1024 String fname = file.getName();
1025 if (dir != null &&
1026 dir.equals(jreFontDirName) &&
1027 jreLucidaFontFiles.contains(fname)) {
1028 continue;
1029 }
1030 PhysicalFont physicalFont = initialiseDeferredFont(fileName);
1031 if (physicalFont != null &&
1032 (physicalFont.getFontName(null).equalsIgnoreCase(name) ||
1033 physicalFont.getFamilyName(null).equalsIgnoreCase(name)) &&
1034 physicalFont.style == style) {
1035 return physicalFont;
1036 }
1037 }
1038 return null;
1039 }
1040
1041 private PhysicalFont findDeferredFont(String name, int style) {
1042
1043 PhysicalFont physicalFont = findJREDeferredFont(name, style);
1044 if (physicalFont != null) {
1045 return physicalFont;
1046 } else {
1047 return findOtherDeferredFont(name, style);
1048 }
1049 }
1050
1051 public void registerDeferredFont(String fileNameKey,
1052 String fullPathName,
1053 String[] nativeNames,
1054 int fontFormat,
1055 boolean useJavaRasterizer,
1056 int fontRank) {
1057 FontRegistrationInfo regInfo =
1058 new FontRegistrationInfo(fullPathName, nativeNames, fontFormat,
1059 useJavaRasterizer, fontRank);
1060 deferredFontFiles.put(fileNameKey, regInfo);
1061 }
1062
1063
1064 public synchronized
1065 PhysicalFont initialiseDeferredFont(String fileNameKey) {
1066
1067 if (fileNameKey == null) {
1068 return null;
1069 }
1070 if (FontUtilities.isLogging()) {
1071 FontUtilities.getLogger()
1072 .info("Opening deferred font file " + fileNameKey);
1073 }
1074
1075 PhysicalFont physicalFont;
1076 FontRegistrationInfo regInfo = deferredFontFiles.get(fileNameKey);
1077 if (regInfo != null) {
1078 deferredFontFiles.remove(fileNameKey);
1079 physicalFont = registerFontFile(regInfo.fontFilePath,
1080 regInfo.nativeNames,
1081 regInfo.fontFormat,
1082 regInfo.javaRasterizer,
1083 regInfo.fontRank);
1084
1085
1086 if (physicalFont != null) {
1087 /* Store the handle, so that if a font is bad, we
1088 * retrieve the substituted font.
1089 */
1090 initialisedFonts.put(fileNameKey, physicalFont.handle);
1091 } else {
1092 initialisedFonts.put(fileNameKey,
1093 getDefaultPhysicalFont().handle);
1094 }
1095 } else {
1096 Font2DHandle handle = initialisedFonts.get(fileNameKey);
1097 if (handle == null) {
1098 /* Probably shouldn't happen, but just in case */
1099 physicalFont = getDefaultPhysicalFont();
1100 } else {
1101 physicalFont = (PhysicalFont)(handle.font2D);
1102 }
1103 }
1104 return physicalFont;
1105 }
1106
1107 public boolean isRegisteredFontFile(String name) {
1108 return registeredFonts.containsKey(name);
1109 }
1110
1111 public PhysicalFont getRegisteredFontFile(String name) {
1112 return registeredFonts.get(name);
1113 }
1114
1115 /* Note that the return value from this method is not always
1116 * derived from this file, and may be null. See addToFontList for
1117 * some explanation of this.
1118 */
1119 public PhysicalFont registerFontFile(String fileName,
1120 String[] nativeNames,
1121 int fontFormat,
1122 boolean useJavaRasterizer,
1123 int fontRank) {
1124
1125 PhysicalFont regFont = registeredFonts.get(fileName);
1126 if (regFont != null) {
1127 return regFont;
1128 }
1129
1130 PhysicalFont physicalFont = null;
1131 try {
1132 String name;
1133
1134 switch (fontFormat) {
1135
1136 case FONTFORMAT_TRUETYPE:
1137 int fn = 0;
1138 TrueTypeFont ttf;
1139 do {
1140 ttf = new TrueTypeFont(fileName, nativeNames, fn++,
1141 useJavaRasterizer);
1142 PhysicalFont pf = addToFontList(ttf, fontRank);
1143 if (physicalFont == null) {
1144 physicalFont = pf;
1145 }
1146 }
1147 while (fn < ttf.getFontCount());
1148 break;
1149
1150 case FONTFORMAT_TYPE1:
1151 Type1Font t1f = new Type1Font(fileName, nativeNames);
1152 physicalFont = addToFontList(t1f, fontRank);
1153 break;
1154
1155 case FONTFORMAT_NATIVE:
1156 NativeFont nf = new NativeFont(fileName, false);
1157 physicalFont = addToFontList(nf, fontRank);
1158 default:
1159
1160 }
1161 if (FontUtilities.isLogging()) {
1162 FontUtilities.getLogger()
1163 .info("Registered file " + fileName + " as font " +
1164 physicalFont + " rank=" + fontRank);
1165 }
1166 } catch (FontFormatException ffe) {
1167 if (FontUtilities.isLogging()) {
1168 FontUtilities.getLogger().warning("Unusable font: " +
1169 fileName + " " + ffe.toString());
1170 }
1171 }
1172 if (physicalFont != null &&
1173 fontFormat != FONTFORMAT_NATIVE) {
1174 registeredFonts.put(fileName, physicalFont);
1175 }
1176 return physicalFont;
1177 }
1178
1179 public void registerFonts(String[] fileNames,
1180 String[][] nativeNames,
1181 int fontCount,
1182 int fontFormat,
1183 boolean useJavaRasterizer,
1184 int fontRank, boolean defer) {
1185
1186 for (int i=0; i < fontCount; i++) {
1187 if (defer) {
1188 registerDeferredFont(fileNames[i],fileNames[i], nativeNames[i],
1189 fontFormat, useJavaRasterizer, fontRank);
1190 } else {
1191 registerFontFile(fileNames[i], nativeNames[i],
1192 fontFormat, useJavaRasterizer, fontRank);
1193 }
1194 }
1195 }
1196
1197 /*
1198 * This is the Physical font used when some other font on the system
1199 * can't be located. There has to be at least one font or the font
1200 * system is not useful and the graphics environment cannot sustain
1201 * the Java platform.
1202 */
1203 public PhysicalFont getDefaultPhysicalFont() {
1204 if (defaultPhysicalFont == null) {
1205 /* findFont2D will load all fonts before giving up the search.
1206 * If the JRE Lucida isn't found (eg because the JRE fonts
1207 * directory is missing), it could find another version of Lucida
1208 * from the host system. This is OK because at that point we are
1209 * trying to gracefully handle/recover from a system
1210 * misconfiguration and this is probably a reasonable substitution.
1211 */
1212 defaultPhysicalFont = (PhysicalFont)
1213 findFont2D("Lucida Sans Regular", Font.PLAIN, NO_FALLBACK);
1214 if (defaultPhysicalFont == null) {
1215 defaultPhysicalFont = (PhysicalFont)
1216 findFont2D("Arial", Font.PLAIN, NO_FALLBACK);
1217 }
1218 if (defaultPhysicalFont == null) {
1219 /* Because of the findFont2D call above, if we reach here, we
1220 * know all fonts have already been loaded, just accept any
1221 * match at this point. If this fails we are in real trouble
1222 * and I don't know how to recover from there being absolutely
1223 * no fonts anywhere on the system.
1224 */
1225 Iterator i = physicalFonts.values().iterator();
1226 if (i.hasNext()) {
1227 defaultPhysicalFont = (PhysicalFont)i.next();
1228 } else {
1229 throw new Error("Probable fatal error:No fonts found.");
1230 }
1231 }
1232 }
1233 return defaultPhysicalFont;
1234 }
1235
1236 public CompositeFont getDefaultLogicalFont(int style) {
1237 return (CompositeFont)findFont2D("dialog", style, NO_FALLBACK);
1238 }
1239
1240 /*
1241 * return String representation of style prepended with "."
1242 * This is useful for performance to avoid unnecessary string operations.
1243 */
1244 private static String dotStyleStr(int num) {
1245 switch(num){
1246 case Font.BOLD:
1247 return ".bold";
1248 case Font.ITALIC:
1249 return ".italic";
1250 case Font.ITALIC | Font.BOLD:
1251 return ".bolditalic";
1252 default:
1253 return ".plain";
1254 }
1255 }
1256
1257 /* This is implemented only on windows and is called from code that
1258 * executes only on windows. This isn't pretty but its not a precedent
1259 * in this file. This very probably should be cleaned up at some point.
1260 */
1261 protected void
1262 populateFontFileNameMap(HashMap<String,String> fontToFileMap,
1263 HashMap<String,String> fontToFamilyNameMap,
1264 HashMap<String,ArrayList<String>>
1265 familyToFontListMap,
1266 Locale locale) {
1267 }
1268
1269 /* Obtained from Platform APIs (windows only)
1270 * Map from lower-case font full name to basename of font file.
1271 * Eg "arial bold" -> ARIALBD.TTF.
1272 * For TTC files, there is a mapping for each font in the file.
1273 */
1274 private HashMap<String,String> fontToFileMap = null;
1275
1276 /* Obtained from Platform APIs (windows only)
1277 * Map from lower-case font full name to the name of its font family
1278 * Eg "arial bold" -> "Arial"
1279 */
1280 private HashMap<String,String> fontToFamilyNameMap = null;
1281
1282 /* Obtained from Platform APIs (windows only)
1283 * Map from a lower-case family name to a list of full names of
1284 * the member fonts, eg:
1285 * "arial" -> ["Arial", "Arial Bold", "Arial Italic","Arial Bold Italic"]
1286 */
1287 private HashMap<String,ArrayList<String>> familyToFontListMap= null;
1288
1289 /* The directories which contain platform fonts */
1290 private String[] pathDirs = null;
1291
1292 private boolean haveCheckedUnreferencedFontFiles;
1293
1294 private String[] getFontFilesFromPath(boolean noType1) {
1295 final FilenameFilter filter;
1296 if (noType1) {
1297 filter = ttFilter;
1298 } else {
1299 filter = new TTorT1Filter();
1300 }
1301 return (String[])AccessController.doPrivileged(new PrivilegedAction() {
1302 public Object run() {
1303 if (pathDirs.length == 1) {
1304 File dir = new File(pathDirs[0]);
1305 String[] files = dir.list(filter);
1306 if (files == null) {
1307 return new String[0];
1308 }
1309 for (int f=0; f<files.length; f++) {
1310 files[f] = files[f].toLowerCase();
1311 }
1312 return files;
1313 } else {
1314 ArrayList<String> fileList = new ArrayList<String>();
1315 for (int i = 0; i< pathDirs.length; i++) {
1316 File dir = new File(pathDirs[i]);
1317 String[] files = dir.list(filter);
1318 if (files == null) {
1319 continue;
1320 }
1321 for (int f=0; f<files.length ; f++) {
1322 fileList.add(files[f].toLowerCase());
1323 }
1324 }
1325 return fileList.toArray(STR_ARRAY);
1326 }
1327 }
1328 });
1329 }
1330
1331 /* This is needed since some windows registry names don't match
1332 * the font names.
1333 * - UPC styled font names have a double space, but the
1334 * registry entry mapping to a file doesn't.
1335 * - Marlett is in a hidden file not listed in the registry
1336 * - The registry advertises that the file david.ttf contains a
1337 * font with the full name "David Regular" when in fact its
1338 * just "David".
1339 * Directly fix up these known cases as this is faster.
1340 * If a font which doesn't match these known cases has no file,
1341 * it may be a font that has been temporarily added to the known set
1342 * or it may be an installed font with a missing registry entry.
1343 * Installed fonts are those in the windows font directories.
1344 * Make a best effort attempt to locate these.
1345 * We obtain the list of TrueType fonts in these directories and
1346 * filter out all the font files we already know about from the registry.
1347 * What remains may be "bad" fonts, duplicate fonts, or perhaps the
1348 * missing font(s) we are looking for.
1349 * Open each of these files to find out.
1350 */
1351 private void resolveWindowsFonts() {
1352
1353 ArrayList<String> unmappedFontNames = null;
1354 for (String font : fontToFamilyNameMap.keySet()) {
1355 String file = fontToFileMap.get(font);
1356 if (file == null) {
1357 if (font.indexOf(" ") > 0) {
1358 String newName = font.replaceFirst(" ", " ");
1359 file = fontToFileMap.get(newName);
1360 /* If this name exists and isn't for a valid name
1361 * replace the mapping to the file with this font
1362 */
1363 if (file != null &&
1364 !fontToFamilyNameMap.containsKey(newName)) {
1365 fontToFileMap.remove(newName);
1366 fontToFileMap.put(font, file);
1367 }
1368 } else if (font.equals("marlett")) {
1369 fontToFileMap.put(font, "marlett.ttf");
1370 } else if (font.equals("david")) {
1371 file = fontToFileMap.get("david regular");
1372 if (file != null) {
1373 fontToFileMap.remove("david regular");
1374 fontToFileMap.put("david", file);
1375 }
1376 } else {
1377 if (unmappedFontNames == null) {
1378 unmappedFontNames = new ArrayList<String>();
1379 }
1380 unmappedFontNames.add(font);
1381 }
1382 }
1383 }
1384
1385 if (unmappedFontNames != null) {
1386 HashSet<String> unmappedFontFiles = new HashSet<String>();
1387
1388 /* Every font key in fontToFileMap ought to correspond to a
1389 * font key in fontToFamilyNameMap. Entries that don't seem
1390 * to correspond are likely fonts that were named differently
1391 * by GDI than in the registry. One known cause of this is when
1392 * Windows has had its regional settings changed so that from
1393 * GDI we get a localised (eg Chinese or Japanese) name for the
1394 * font, but the registry retains the English version of the name
1395 * that corresponded to the "install" locale for windows.
1396 * Since we are in this code block because there are unmapped
1397 * font names, we can look to find unused font->file mappings
1398 * and then open the files to read the names. We don't generally
1399 * want to open font files, as its a performance hit, but this
1400 * occurs only for a small number of fonts on specific system
1401 * configs - ie is believed that a "true" Japanese windows would
1402 * have JA names in the registry too.
1403 * Clone fontToFileMap and remove from the clone all keys which
1404 * match a fontToFamilyNameMap key. What remains maps to the
1405 * files we want to open to find the fonts GDI returned.
1406 * A font in such a file is added to the fontToFileMap after
1407 * checking its one of the unmappedFontNames we are looking for.
1408 * The original name that didn't map is removed from fontToFileMap
1409 * so essentially this "fixes up" fontToFileMap to use the same
1410 * name as GDI.
1411 * Also note that typically the fonts for which this occurs in
1412 * CJK locales are TTC fonts and not all fonts in a TTC may have
1413 * localised names. Eg MSGOTHIC.TTC contains 3 fonts and one of
1414 * them "MS UI Gothic" has no JA name whereas the other two do.
1415 * So not every font in these files is unmapped or new.
1416 */
1417 HashMap<String,String> ffmapCopy =
1418 (HashMap<String,String>)(fontToFileMap.clone());
1419 for (String key : fontToFamilyNameMap.keySet()) {
1420 ffmapCopy.remove(key);
1421 }
1422 for (String key : ffmapCopy.keySet()) {
1423 unmappedFontFiles.add(ffmapCopy.get(key));
1424 fontToFileMap.remove(key);
1425 }
1426
1427 resolveFontFiles(unmappedFontFiles, unmappedFontNames);
1428
1429 /* If there are still unmapped font names, this means there's
1430 * something that wasn't in the registry. We need to get all
1431 * the font files directly and look at the ones that weren't
1432 * found in the registry.
1433 */
1434 if (unmappedFontNames.size() > 0) {
1435
1436 /* getFontFilesFromPath() returns all lower case names.
1437 * To compare we also need lower case
1438 * versions of the names from the registry.
1439 */
1440 ArrayList<String> registryFiles = new ArrayList<String>();
1441
1442 for (String regFile : fontToFileMap.values()) {
1443 registryFiles.add(regFile.toLowerCase());
1444 }
1445 /* We don't look for Type1 files here as windows will
1446 * not enumerate these, so aren't useful in reconciling
1447 * GDI's unmapped files. We do find these later when
1448 * we enumerate all fonts.
1449 */
1450 for (String pathFile : getFontFilesFromPath(true)) {
1451 if (!registryFiles.contains(pathFile)) {
1452 unmappedFontFiles.add(pathFile);
1453 }
1454 }
1455
1456 resolveFontFiles(unmappedFontFiles, unmappedFontNames);
1457 }
1458
1459 /* remove from the set of names that will be returned to the
1460 * user any fonts that can't be mapped to files.
1461 */
1462 if (unmappedFontNames.size() > 0) {
1463 int sz = unmappedFontNames.size();
1464 for (int i=0; i<sz; i++) {
1465 String name = unmappedFontNames.get(i);
1466 String familyName = fontToFamilyNameMap.get(name);
1467 if (familyName != null) {
1468 ArrayList family = familyToFontListMap.get(familyName);
1469 if (family != null) {
1470 if (family.size() <= 1) {
1471 familyToFontListMap.remove(familyName);
1472 }
1473 }
1474 }
1475 fontToFamilyNameMap.remove(name);
1476 if (FontUtilities.isLogging()) {
1477 FontUtilities.getLogger()
1478 .info("No file for font:" + name);
1479 }
1480 }
1481 }
1482 }
1483 }
1484
1485 /**
1486 * In some cases windows may have fonts in the fonts folder that
1487 * don't show up in the registry or in the GDI calls to enumerate fonts.
1488 * The only way to find these is to list the directory. We invoke this
1489 * only in getAllFonts/Families, so most searches for a specific
1490 * font that is satisfied by the GDI/registry calls don't take the
1491 * additional hit of listing the directory. This hit is small enough
1492 * that its not significant in these 'enumerate all the fonts' cases.
1493 * The basic approach is to cross-reference the files windows found
1494 * with the ones in the directory listing approach, and for each
1495 * in the latter list that is missing from the former list, register it.
1496 */
1497 private synchronized void checkForUnreferencedFontFiles() {
1498 if (haveCheckedUnreferencedFontFiles) {
1499 return;
1500 }
1501 haveCheckedUnreferencedFontFiles = true;
1502 if (!FontUtilities.isWindows) {
1503 return;
1504 }
1505 /* getFontFilesFromPath() returns all lower case names.
1506 * To compare we also need lower case
1507 * versions of the names from the registry.
1508 */
1509 ArrayList<String> registryFiles = new ArrayList<String>();
1510 for (String regFile : fontToFileMap.values()) {
1511 registryFiles.add(regFile.toLowerCase());
1512 }
1513
1514 /* To avoid any issues with concurrent modification, create
1515 * copies of the existing maps, add the new fonts into these
1516 * and then replace the references to the old ones with the
1517 * new maps. ConcurrentHashmap is another option but its a lot
1518 * more changes and with this exception, these maps are intended
1519 * to be static.
1520 */
1521 HashMap<String,String> fontToFileMap2 = null;
1522 HashMap<String,String> fontToFamilyNameMap2 = null;
1523 HashMap<String,ArrayList<String>> familyToFontListMap2 = null;;
1524
1525 for (String pathFile : getFontFilesFromPath(false)) {
1526 if (!registryFiles.contains(pathFile)) {
1527 if (FontUtilities.isLogging()) {
1528 FontUtilities.getLogger()
1529 .info("Found non-registry file : " + pathFile);
1530 }
1531 PhysicalFont f = registerFontFile(getPathName(pathFile));
1532 if (f == null) {
1533 continue;
1534 }
1535 if (fontToFileMap2 == null) {
1536 fontToFileMap2 = new HashMap<String,String>(fontToFileMap);
1537 fontToFamilyNameMap2 =
1538 new HashMap<String,String>(fontToFamilyNameMap);
1539 familyToFontListMap2 = new
1540 HashMap<String,ArrayList<String>>(familyToFontListMap);
1541 }
1542 String fontName = f.getFontName(null);
1543 String family = f.getFamilyName(null);
1544 String familyLC = family.toLowerCase();
1545 fontToFamilyNameMap2.put(fontName, family);
1546 fontToFileMap2.put(fontName, pathFile);
1547 ArrayList<String> fonts = familyToFontListMap2.get(familyLC);
1548 if (fonts == null) {
1549 fonts = new ArrayList<String>();
1550 } else {
1551 fonts = new ArrayList<String>(fonts);
1552 }
1553 fonts.add(fontName);
1554 familyToFontListMap2.put(familyLC, fonts);
1555 }
1556 }
1557 if (fontToFileMap2 != null) {
1558 fontToFileMap = fontToFileMap2;
1559 familyToFontListMap = familyToFontListMap2;
1560 fontToFamilyNameMap = fontToFamilyNameMap2;
1561 }
1562 }
1563
1564 private void resolveFontFiles(HashSet<String> unmappedFiles,
1565 ArrayList<String> unmappedFonts) {
1566
1567 Locale l = SunToolkit.getStartupLocale();
1568
1569 for (String file : unmappedFiles) {
1570 try {
1571 int fn = 0;
1572 TrueTypeFont ttf;
1573 String fullPath = getPathName(file);
1574 if (FontUtilities.isLogging()) {
1575 FontUtilities.getLogger()
1576 .info("Trying to resolve file " + fullPath);
1577 }
1578 do {
1579 ttf = new TrueTypeFont(fullPath, null, fn++, false);
1580 // prefer the font's locale name.
1581 String fontName = ttf.getFontName(l).toLowerCase();
1582 if (unmappedFonts.contains(fontName)) {
1583 fontToFileMap.put(fontName, file);
1584 unmappedFonts.remove(fontName);
1585 if (FontUtilities.isLogging()) {
1586 FontUtilities.getLogger()
1587 .info("Resolved absent registry entry for " +
1588 fontName + " located in " + fullPath);
1589 }
1590 }
1591 }
1592 while (fn < ttf.getFontCount());
1593 } catch (Exception e) {
1594 }
1595 }
1596 }
1597
1598 /* Hardwire the English names and expected file names of fonts
1599 * commonly used at start up. Avoiding until later even the small
1600 * cost of calling platform APIs to locate these can help.
1601 * The code that registers these fonts needs to "bail" if any
1602 * of the files do not exist, so it will verify the existence of
1603 * all non-null file names first.
1604 * They are added in to a map with nominally the first
1605 * word in the name of the family as the key. In all the cases
1606 * we are using the the family name is a single word, and as is
1607 * more or less required the family name is the initial sequence
1608 * in a full name. So lookup first finds the matching description,
1609 * then registers the whole family, returning the right font.
1610 */
1611 public static class FamilyDescription {
1612 public String familyName;
1613 public String plainFullName;
1614 public String boldFullName;
1615 public String italicFullName;
1616 public String boldItalicFullName;
1617 public String plainFileName;
1618 public String boldFileName;
1619 public String italicFileName;
1620 public String boldItalicFileName;
1621 }
1622
1623 static HashMap<String, FamilyDescription> platformFontMap;
1624
1625 /**
1626 * default implementation does nothing.
1627 */
1628 public HashMap<String, FamilyDescription> populateHardcodedFileNameMap() {
1629 return new HashMap<String, FamilyDescription>(0);
1630 }
1631
1632 Font2D findFontFromPlatformMap(String lcName, int style) {
1633 if (platformFontMap == null) {
1634 platformFontMap = populateHardcodedFileNameMap();
1635 }
1636
1637 if (platformFontMap == null || platformFontMap.size() == 0) {
1638 return null;
1639 }
1640
1641 int spaceIndex = lcName.indexOf(' ');
1642 String firstWord = lcName;
1643 if (spaceIndex > 0) {
1644 firstWord = lcName.substring(0, spaceIndex);
1645 }
1646
1647 FamilyDescription fd = platformFontMap.get(firstWord);
1648 if (fd == null) {
1649 return null;
1650 }
1651 /* Once we've established that its at least the first word,
1652 * we need to dig deeper to make sure its a match for either
1653 * a full name, or the family name, to make sure its not
1654 * a request for some other font that just happens to start
1655 * with the same first word.
1656 */
1657 int styleIndex = -1;
1658 if (lcName.equalsIgnoreCase(fd.plainFullName)) {
1659 styleIndex = 0;
1660 } else if (lcName.equalsIgnoreCase(fd.boldFullName)) {
1661 styleIndex = 1;
1662 } else if (lcName.equalsIgnoreCase(fd.italicFullName)) {
1663 styleIndex = 2;
1664 } else if (lcName.equalsIgnoreCase(fd.boldItalicFullName)) {
1665 styleIndex = 3;
1666 }
1667 if (styleIndex == -1 && !lcName.equalsIgnoreCase(fd.familyName)) {
1668 return null;
1669 }
1670
1671 String plainFile = null, boldFile = null,
1672 italicFile = null, boldItalicFile = null;
1673
1674 boolean failure = false;
1675 /* In a terminal server config, its possible that getPathName()
1676 * will return null, if the file doesn't exist, hence the null
1677 * checks on return. But in the normal client config we need to
1678 * follow this up with a check to see if all the files really
1679 * exist for the non-null paths.
1680 */
1681 getPlatformFontDirs(noType1Font);
1682
1683 if (fd.plainFileName != null) {
1684 plainFile = getPathName(fd.plainFileName);
1685 if (plainFile == null) {
1686 failure = true;
1687 }
1688 }
1689
1690 if (fd.boldFileName != null) {
1691 boldFile = getPathName(fd.boldFileName);
1692 if (boldFile == null) {
1693 failure = true;
1694 }
1695 }
1696
1697 if (fd.italicFileName != null) {
1698 italicFile = getPathName(fd.italicFileName);
1699 if (italicFile == null) {
1700 failure = true;
1701 }
1702 }
1703
1704 if (fd.boldItalicFileName != null) {
1705 boldItalicFile = getPathName(fd.boldItalicFileName);
1706 if (boldItalicFile == null) {
1707 failure = true;
1708 }
1709 }
1710
1711 if (failure) {
1712 if (FontUtilities.isLogging()) {
1713 FontUtilities.getLogger().
1714 info("Hardcoded file missing looking for " + lcName);
1715 }
1716 platformFontMap.remove(firstWord);
1717 return null;
1718 }
1719
1720 /* Some of these may be null,as not all styles have to exist */
1721 final String[] files = {
1722 plainFile, boldFile, italicFile, boldItalicFile } ;
1723
1724 failure = java.security.AccessController.doPrivileged(
1725 new java.security.PrivilegedAction<Boolean>() {
1726 public Boolean run() {
1727 for (int i=0; i<files.length; i++) {
1728 if (files[i] == null) {
1729 continue;
1730 }
1731 File f = new File(files[i]);
1732 if (!f.exists()) {
1733 return Boolean.TRUE;
1734 }
1735 }
1736 return Boolean.FALSE;
1737 }
1738 });
1739
1740 if (failure) {
1741 if (FontUtilities.isLogging()) {
1742 FontUtilities.getLogger().
1743 info("Hardcoded file missing looking for " + lcName);
1744 }
1745 platformFontMap.remove(firstWord);
1746 return null;
1747 }
1748
1749 /* If we reach here we know that we have all the files we
1750 * expect, so all should be fine so long as the contents
1751 * are what we'd expect. Now on to registering the fonts.
1752 * Currently this code only looks for TrueType fonts, so format
1753 * and rank can be specified without looking at the filename.
1754 */
1755 Font2D font = null;
1756 for (int f=0;f<files.length;f++) {
1757 if (files[f] == null) {
1758 continue;
1759 }
1760 PhysicalFont pf =
1761 registerFontFile(files[f], null,
1762 FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK);
1763 if (f == styleIndex) {
1764 font = pf;
1765 }
1766 }
1767
1768
1769 /* Two general cases need a bit more work here.
1770 * 1) If font is null, then it was perhaps a request for a
1771 * non-existent font, such as "Tahoma Italic", or a family name -
1772 * where family and full name of the plain font differ.
1773 * Fall back to finding the closest one in the family.
1774 * This could still fail if a client specified "Segoe" instead of
1775 * "Segoe UI".
1776 * 2) The request is of the form "MyFont Bold", style=Font.ITALIC,
1777 * and so we want to see if there's a Bold Italic font, or
1778 * "MyFamily", style=Font.BOLD, and we may have matched the plain,
1779 * but now need to revise that to the BOLD font.
1780 */
1781 FontFamily fontFamily = FontFamily.getFamily(fd.familyName);
1782 if (fontFamily != null) {
1783 if (font == null) {
1784 font = fontFamily.getFont(style);
1785 if (font == null) {
1786 font = fontFamily.getClosestStyle(style);
1787 }
1788 } else if (style > 0 && style != font.style) {
1789 style |= font.style;
1790 font = fontFamily.getFont(style);
1791 if (font == null) {
1792 font = fontFamily.getClosestStyle(style);
1793 }
1794 }
1795 }
1796
1797 return font;
1798 }
1799 private synchronized HashMap<String,String> getFullNameToFileMap() {
1800 if (fontToFileMap == null) {
1801
1802 pathDirs = getPlatformFontDirs(noType1Font);
1803
1804 fontToFileMap = new HashMap<String,String>(100);
1805 fontToFamilyNameMap = new HashMap<String,String>(100);
1806 familyToFontListMap = new HashMap<String,ArrayList<String>>(50);
1807 populateFontFileNameMap(fontToFileMap,
1808 fontToFamilyNameMap,
1809 familyToFontListMap,
1810 Locale.ENGLISH);
1811 if (FontUtilities.isWindows) {
1812 resolveWindowsFonts();
1813 }
1814 if (FontUtilities.isLogging()) {
1815 logPlatformFontInfo();
1816 }
1817 }
1818 return fontToFileMap;
1819 }
1820
1821 private void logPlatformFontInfo() {
1822 PlatformLogger logger = FontUtilities.getLogger();
1823 for (int i=0; i< pathDirs.length;i++) {
1824 logger.info("fontdir="+pathDirs[i]);
1825 }
1826 for (String keyName : fontToFileMap.keySet()) {
1827 logger.info("font="+keyName+" file="+ fontToFileMap.get(keyName));
1828 }
1829 for (String keyName : fontToFamilyNameMap.keySet()) {
1830 logger.info("font="+keyName+" family="+
1831 fontToFamilyNameMap.get(keyName));
1832 }
1833 for (String keyName : familyToFontListMap.keySet()) {
1834 logger.info("family="+keyName+ " fonts="+
1835 familyToFontListMap.get(keyName));
1836 }
1837 }
1838
1839 /* Note this return list excludes logical fonts and JRE fonts */
1840 protected String[] getFontNamesFromPlatform() {
1841 if (getFullNameToFileMap().size() == 0) {
1842 return null;
1843 }
1844 checkForUnreferencedFontFiles();
1845 /* This odd code with TreeMap is used to preserve a historical
1846 * behaviour wrt the sorting order .. */
1847 ArrayList<String> fontNames = new ArrayList<String>();
1848 for (ArrayList<String> a : familyToFontListMap.values()) {
1849 for (String s : a) {
1850 fontNames.add(s);
1851 }
1852 }
1853 return fontNames.toArray(STR_ARRAY);
1854 }
1855
1856 public boolean gotFontsFromPlatform() {
1857 return getFullNameToFileMap().size() != 0;
1858 }
1859
1860 public String getFileNameForFontName(String fontName) {
1861 String fontNameLC = fontName.toLowerCase(Locale.ENGLISH);
1862 return fontToFileMap.get(fontNameLC);
1863 }
1864
1865 private PhysicalFont registerFontFile(String file) {
1866 if (new File(file).isAbsolute() &&
1867 !registeredFonts.contains(file)) {
1868 int fontFormat = FONTFORMAT_NONE;
1869 int fontRank = Font2D.UNKNOWN_RANK;
1870 if (ttFilter.accept(null, file)) {
1871 fontFormat = FONTFORMAT_TRUETYPE;
1872 fontRank = Font2D.TTF_RANK;
1873 } else if
1874 (t1Filter.accept(null, file)) {
1875 fontFormat = FONTFORMAT_TYPE1;
1876 fontRank = Font2D.TYPE1_RANK;
1877 }
1878 if (fontFormat == FONTFORMAT_NONE) {
1879 return null;
1880 }
1881 return registerFontFile(file, null, fontFormat, false, fontRank);
1882 }
1883 return null;
1884 }
1885
1886 /* Used to register any font files that are found by platform APIs
1887 * that weren't previously found in the standard font locations.
1888 * the isAbsolute() check is needed since that's whats stored in the
1889 * set, and on windows, the fonts in the system font directory that
1890 * are in the fontToFileMap are just basenames. We don't want to try
1891 * to register those again, but we do want to register other registry
1892 * installed fonts.
1893 */
1894 protected void registerOtherFontFiles(HashSet registeredFontFiles) {
1895 if (getFullNameToFileMap().size() == 0) {
1896 return;
1897 }
1898 for (String file : fontToFileMap.values()) {
1899 registerFontFile(file);
1900 }
1901 }
1902
1903 public boolean
1904 getFamilyNamesFromPlatform(TreeMap<String,String> familyNames,
1905 Locale requestedLocale) {
1906 if (getFullNameToFileMap().size() == 0) {
1907 return false;
1908 }
1909 checkForUnreferencedFontFiles();
1910 for (String name : fontToFamilyNameMap.values()) {
1911 familyNames.put(name.toLowerCase(requestedLocale), name);
1912 }
1913 return true;
1914 }
1915
1916 /* Path may be absolute or a base file name relative to one of
1917 * the platform font directories
1918 */
1919 private String getPathName(final String s) {
1920 File f = new File(s);
1921 if (f.isAbsolute()) {
1922 return s;
1923 } else if (pathDirs.length==1) {
1924 return pathDirs[0] + File.separator + s;
1925 } else {
1926 String path = java.security.AccessController.doPrivileged(
1927 new java.security.PrivilegedAction<String>() {
1928 public String run() {
1929 for (int p=0; p<pathDirs.length; p++) {
1930 File f = new File(pathDirs[p] +File.separator+ s);
1931 if (f.exists()) {
1932 return f.getAbsolutePath();
1933 }
1934 }
1935 return null;
1936 }
1937 });
1938 if (path != null) {
1939 return path;
1940 }
1941 }
1942 return s; // shouldn't happen, but harmless
1943 }
1944
1945 /* lcName is required to be lower case for use as a key.
1946 * lcName may be a full name, or a family name, and style may
1947 * be specified in addition to either of these. So be sure to
1948 * get the right one. Since an app *could* ask for "Foo Regular"
1949 * and later ask for "Foo Italic", if we don't register all the
1950 * styles, then logic in findFont2D may try to style the original
1951 * so we register the entire family if we get a match here.
1952 * This is still a big win because this code is invoked where
1953 * otherwise we would register all fonts.
1954 * It's also useful for the case where "Foo Bold" was specified with
1955 * style Font.ITALIC, as we would want in that case to try to return
1956 * "Foo Bold Italic" if it exists, and it is only by locating "Foo Bold"
1957 * and opening it that we really "know" it's Bold, and can look for
1958 * a font that supports that and the italic style.
1959 * The code in here is not overtly windows-specific but in fact it
1960 * is unlikely to be useful as is on other platforms. It is maintained
1961 * in this shared source file to be close to its sole client and
1962 * because so much of the logic is intertwined with the logic in
1963 * findFont2D.
1964 */
1965 private Font2D findFontFromPlatform(String lcName, int style) {
1966 if (getFullNameToFileMap().size() == 0) {
1967 return null;
1968 }
1969
1970 ArrayList<String> family = null;
1971 String fontFile = null;
1972 String familyName = fontToFamilyNameMap.get(lcName);
1973 if (familyName != null) {
1974 fontFile = fontToFileMap.get(lcName);
1975 family = familyToFontListMap.get
1976 (familyName.toLowerCase(Locale.ENGLISH));
1977 } else {
1978 family = familyToFontListMap.get(lcName); // is lcName is a family?
1979 if (family != null && family.size() > 0) {
1980 String lcFontName = family.get(0).toLowerCase(Locale.ENGLISH);
1981 if (lcFontName != null) {
1982 familyName = fontToFamilyNameMap.get(lcFontName);
1983 }
1984 }
1985 }
1986 if (family == null || familyName == null) {
1987 return null;
1988 }
1989 String [] fontList = (String[])family.toArray(STR_ARRAY);
1990 if (fontList.length == 0) {
1991 return null;
1992 }
1993
1994 /* first check that for every font in this family we can find
1995 * a font file. The specific reason for doing this is that
1996 * in at least one case on Windows a font has the face name "David"
1997 * but the registry entry is "David Regular". That is the "unique"
1998 * name of the font but in other cases the registry contains the
1999 * "full" name. See the specifications of name ids 3 and 4 in the
2000 * TrueType 'name' table.
2001 * In general this could cause a problem that we fail to register
2002 * if we all members of a family that we may end up mapping to
2003 * the wrong font member: eg return Bold when Plain is needed.
2004 */
2005 for (int f=0;f<fontList.length;f++) {
2006 String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH);
2007 String fileName = fontToFileMap.get(fontNameLC);
2008 if (fileName == null) {
2009 if (FontUtilities.isLogging()) {
2010 FontUtilities.getLogger()
2011 .info("Platform lookup : No file for font " +
2012 fontList[f] + " in family " +familyName);
2013 }
2014 return null;
2015 }
2016 }
2017
2018 /* Currently this code only looks for TrueType fonts, so format
2019 * and rank can be specified without looking at the filename.
2020 */
2021 PhysicalFont physicalFont = null;
2022 if (fontFile != null) {
2023 physicalFont = registerFontFile(getPathName(fontFile), null,
2024 FONTFORMAT_TRUETYPE, false,
2025 Font2D.TTF_RANK);
2026 }
2027 /* Register all fonts in this family. */
2028 for (int f=0;f<fontList.length;f++) {
2029 String fontNameLC = fontList[f].toLowerCase(Locale.ENGLISH);
2030 String fileName = fontToFileMap.get(fontNameLC);
2031 if (fontFile != null && fontFile.equals(fileName)) {
2032 continue;
2033 }
2034 /* Currently this code only looks for TrueType fonts, so format
2035 * and rank can be specified without looking at the filename.
2036 */
2037 registerFontFile(getPathName(fileName), null,
2038 FONTFORMAT_TRUETYPE, false, Font2D.TTF_RANK);
2039 }
2040
2041 Font2D font = null;
2042 FontFamily fontFamily = FontFamily.getFamily(familyName);
2043 /* Handle case where request "MyFont Bold", style=Font.ITALIC */
2044 if (physicalFont != null) {
2045 style |= physicalFont.style;
2046 }
2047 if (fontFamily != null) {
2048 font = fontFamily.getFont(style);
2049 if (font == null) {
2050 font = fontFamily.getClosestStyle(style);
2051 }
2052 }
2053 return font;
2054 }
2055
2056 private ConcurrentHashMap<String, Font2D> fontNameCache =
2057 new ConcurrentHashMap<String, Font2D>();
2058
2059 /*
2060 * The client supplies a name and a style.
2061 * The name could be a family name, or a full name.
2062 * A font may exist with the specified style, or it may
2063 * exist only in some other style. For non-native fonts the scaler
2064 * may be able to emulate the required style.
2065 */
2066 public Font2D findFont2D(String name, int style, int fallback) {
2067 String lowerCaseName = name.toLowerCase(Locale.ENGLISH);
2068 String mapName = lowerCaseName + dotStyleStr(style);
2069 Font2D font;
2070
2071 /* If preferLocaleFonts() or preferProportionalFonts() has been
2072 * called we may be using an alternate set of composite fonts in this
2073 * app context. The presence of a pre-built name map indicates whether
2074 * this is so, and gives access to the alternate composite for the
2075 * name.
2076 */
2077 if (_usingPerAppContextComposites) {
2078 ConcurrentHashMap<String, Font2D> altNameCache =
2079 (ConcurrentHashMap<String, Font2D>)
2080 AppContext.getAppContext().get(CompositeFont.class);
2081 if (altNameCache != null) {
2082 font = (Font2D)altNameCache.get(mapName);
2083 } else {
2084 font = null;
2085 }
2086 } else {
2087 font = fontNameCache.get(mapName);
2088 }
2089 if (font != null) {
2090 return font;
2091 }
2092
2093 if (FontUtilities.isLogging()) {
2094 FontUtilities.getLogger().info("Search for font: " + name);
2095 }
2096
2097 // The check below is just so that the bitmap fonts being set by
2098 // AWT and Swing thru the desktop properties do not trigger the
2099 // the load fonts case. The two bitmap fonts are now mapped to
2100 // appropriate equivalents for serif and sansserif.
2101 // Note that the cost of this comparison is only for the first
2102 // call until the map is filled.
2103 if (FontUtilities.isWindows) {
2104 if (lowerCaseName.equals("ms sans serif")) {
2105 name = "sansserif";
2106 } else if (lowerCaseName.equals("ms serif")) {
2107 name = "serif";
2108 }
2109 }
2110
2111 /* This isn't intended to support a client passing in the
2112 * string default, but if a client passes in null for the name
2113 * the java.awt.Font class internally substitutes this name.
2114 * So we need to recognise it here to prevent a loadFonts
2115 * on the unrecognised name. The only potential problem with
2116 * this is it would hide any real font called "default"!
2117 * But that seems like a potential problem we can ignore for now.
2118 */
2119 if (lowerCaseName.equals("default")) {
2120 name = "dialog";
2121 }
2122
2123 /* First see if its a family name. */
2124 FontFamily family = FontFamily.getFamily(name);
2125 if (family != null) {
2126 font = family.getFontWithExactStyleMatch(style);
2127 if (font == null) {
2128 font = findDeferredFont(name, style);
2129 }
2130 if (font == null) {
2131 font = family.getFont(style);
2132 }
2133 if (font == null) {
2134 font = family.getClosestStyle(style);
2135 }
2136 if (font != null) {
2137 fontNameCache.put(mapName, font);
2138 return font;
2139 }
2140 }
2141
2142 /* If it wasn't a family name, it should be a full name of
2143 * either a composite, or a physical font
2144 */
2145 font = fullNameToFont.get(lowerCaseName);
2146 if (font != null) {
2147 /* Check that the requested style matches the matched font's style.
2148 * But also match style automatically if the requested style is
2149 * "plain". This because the existing behaviour is that the fonts
2150 * listed via getAllFonts etc always list their style as PLAIN.
2151 * This does lead to non-commutative behaviours where you might
2152 * start with "Lucida Sans Regular" and ask for a BOLD version
2153 * and get "Lucida Sans DemiBold" but if you ask for the PLAIN
2154 * style of "Lucida Sans DemiBold" you get "Lucida Sans DemiBold".
2155 * This consistent however with what happens if you have a bold
2156 * version of a font and no plain version exists - alg. styling
2157 * doesn't "unbolden" the font.
2158 */
2159 if (font.style == style || style == Font.PLAIN) {
2160 fontNameCache.put(mapName, font);
2161 return font;
2162 } else {
2163 /* If it was a full name like "Lucida Sans Regular", but
2164 * the style requested is "bold", then we want to see if
2165 * there's the appropriate match against another font in
2166 * that family before trying to load all fonts, or applying a
2167 * algorithmic styling
2168 */
2169 family = FontFamily.getFamily(font.getFamilyName(null));
2170 if (family != null) {
2171 Font2D familyFont = family.getFont(style|font.style);
2172 /* We exactly matched the requested style, use it! */
2173 if (familyFont != null) {
2174 fontNameCache.put(mapName, familyFont);
2175 return familyFont;
2176 } else {
2177 /* This next call is designed to support the case
2178 * where bold italic is requested, and if we must
2179 * style, then base it on either bold or italic -
2180 * not on plain!
2181 */
2182 familyFont = family.getClosestStyle(style|font.style);
2183 if (familyFont != null) {
2184 /* The next check is perhaps one
2185 * that shouldn't be done. ie if we get this
2186 * far we have probably as close a match as we
2187 * are going to get. We could load all fonts to
2188 * see if somehow some parts of the family are
2189 * loaded but not all of it.
2190 */
2191 if (familyFont.canDoStyle(style|font.style)) {
2192 fontNameCache.put(mapName, familyFont);
2193 return familyFont;
2194 }
2195 }
2196 }
2197 }
2198 }
2199 }
2200
2201 if (FontUtilities.isWindows) {
2202
2203 font = findFontFromPlatformMap(lowerCaseName, style);
2204 if (FontUtilities.isLogging()) {
2205 FontUtilities.getLogger()
2206 .info("findFontFromPlatformMap returned " + font);
2207 }
2208 if (font != null) {
2209 fontNameCache.put(mapName, font);
2210 return font;
2211 }
2212
2213 /* Don't want Windows to return a Lucida Sans font from
2214 * C:\Windows\Fonts
2215 */
2216 if (deferredFontFiles.size() > 0) {
2217 font = findJREDeferredFont(lowerCaseName, style);
2218 if (font != null) {
2219 fontNameCache.put(mapName, font);
2220 return font;
2221 }
2222 }
2223 font = findFontFromPlatform(lowerCaseName, style);
2224 if (font != null) {
2225 if (FontUtilities.isLogging()) {
2226 FontUtilities.getLogger()
2227 .info("Found font via platform API for request:\"" +
2228 name + "\":, style="+style+
2229 " found font: " + font);
2230 }
2231 fontNameCache.put(mapName, font);
2232 return font;
2233 }
2234 }
2235
2236 /* If reach here and no match has been located, then if there are
2237 * uninitialised deferred fonts, load as many of those as needed
2238 * to find the deferred font. If none is found through that
2239 * search continue on.
2240 * There is possibly a minor issue when more than one
2241 * deferred font implements the same font face. Since deferred
2242 * fonts are only those in font configuration files, this is a
2243 * controlled situation, the known case being Solaris euro_fonts
2244 * versions of Arial, Times New Roman, Courier New. However
2245 * the larger font will transparently replace the smaller one
2246 * - see addToFontList() - when it is needed by the composite font.
2247 */
2248 if (deferredFontFiles.size() > 0) {
2249 font = findDeferredFont(name, style);
2250 if (font != null) {
2251 fontNameCache.put(mapName, font);
2252 return font;
2253 }
2254 }
2255
2256 /* Some apps use deprecated 1.0 names such as helvetica and courier. On
2257 * Solaris these are Type1 fonts in /usr/openwin/lib/X11/fonts/Type1.
2258 * If running on Solaris will register all the fonts in this
2259 * directory.
2260 * May as well register the whole directory without actually testing
2261 * the font name is one of the deprecated names as the next step would
2262 * load all fonts which are in this directory anyway.
2263 * In the event that this lookup is successful it potentially "hides"
2264 * TrueType versions of such fonts that are elsewhere but since they
2265 * do not exist on Solaris this is not a problem.
2266 * Set a flag to indicate we've done this registration to avoid
2267 * repetition and more seriously, to avoid recursion.
2268 */
2269 if (FontUtilities.isSolaris &&!loaded1dot0Fonts) {
2270 /* "timesroman" is a special case since that's not the
2271 * name of any known font on Solaris or elsewhere.
2272 */
2273 if (lowerCaseName.equals("timesroman")) {
2274 font = findFont2D("serif", style, fallback);
2275 fontNameCache.put(mapName, font);
2276 }
2277 register1dot0Fonts();
2278 loaded1dot0Fonts = true;
2279 Font2D ff = findFont2D(name, style, fallback);
2280 return ff;
2281 }
2282
2283 /* We check for application registered fonts before
2284 * explicitly loading all fonts as if necessary the registration
2285 * code will have done so anyway. And we don't want to needlessly
2286 * load the actual files for all fonts.
2287 * Just as for installed fonts we check for family before fullname.
2288 * We do not add these fonts to fontNameCache for the
2289 * app context case which eliminates the overhead of a per context
2290 * cache for these.
2291 */
2292
2293 if (fontsAreRegistered || fontsAreRegisteredPerAppContext) {
2294 Hashtable<String, FontFamily> familyTable = null;
2295 Hashtable<String, Font2D> nameTable;
2296
2297 if (fontsAreRegistered) {
2298 familyTable = createdByFamilyName;
2299 nameTable = createdByFullName;
2300 } else {
2301 AppContext appContext = AppContext.getAppContext();
2302 familyTable =
2303 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
2304 nameTable =
2305 (Hashtable<String,Font2D>)appContext.get(regFullNameKey);
2306 }
2307
2308 family = familyTable.get(lowerCaseName);
2309 if (family != null) {
2310 font = family.getFontWithExactStyleMatch(style);
2311 if (font == null) {
2312 font = family.getFont(style);
2313 }
2314 if (font == null) {
2315 font = family.getClosestStyle(style);
2316 }
2317 if (font != null) {
2318 if (fontsAreRegistered) {
2319 fontNameCache.put(mapName, font);
2320 }
2321 return font;
2322 }
2323 }
2324 font = nameTable.get(lowerCaseName);
2325 if (font != null) {
2326 if (fontsAreRegistered) {
2327 fontNameCache.put(mapName, font);
2328 }
2329 return font;
2330 }
2331 }
2332
2333 /* If reach here and no match has been located, then if all fonts
2334 * are not yet loaded, do so, and then recurse.
2335 */
2336 if (!loadedAllFonts) {
2337 if (FontUtilities.isLogging()) {
2338 FontUtilities.getLogger()
2339 .info("Load fonts looking for:" + name);
2340 }
2341 loadFonts();
2342 loadedAllFonts = true;
2343 return findFont2D(name, style, fallback);
2344 }
2345
2346 if (!loadedAllFontFiles) {
2347 if (FontUtilities.isLogging()) {
2348 FontUtilities.getLogger()
2349 .info("Load font files looking for:" + name);
2350 }
2351 loadFontFiles();
2352 loadedAllFontFiles = true;
2353 return findFont2D(name, style, fallback);
2354 }
2355
2356 /* The primary name is the locale default - ie not US/English but
2357 * whatever is the default in this locale. This is the way it always
2358 * has been but may be surprising to some developers if "Arial Regular"
2359 * were hard-coded in their app and yet "Arial Regular" was not the
2360 * default name. Fortunately for them, as a consequence of the JDK
2361 * supporting returning names and family names for arbitrary locales,
2362 * we also need to support searching all localised names for a match.
2363 * But because this case of the name used to reference a font is not
2364 * the same as the default for this locale is rare, it makes sense to
2365 * search a much shorter list of default locale names and only go to
2366 * a longer list of names in the event that no match was found.
2367 * So add here code which searches localised names too.
2368 * As in 1.4.x this happens only after loading all fonts, which
2369 * is probably the right order.
2370 */
2371 if ((font = findFont2DAllLocales(name, style)) != null) {
2372 fontNameCache.put(mapName, font);
2373 return font;
2374 }
2375
2376 /* Perhaps its a "compatibility" name - timesroman, helvetica,
2377 * or courier, which 1.0 apps used for logical fonts.
2378 * We look for these "late" after a loadFonts as we must not
2379 * hide real fonts of these names.
2380 * Map these appropriately:
2381 * On windows this means according to the rules specified by the
2382 * FontConfiguration : do it only for encoding==Cp1252
2383 *
2384 * REMIND: this is something we plan to remove.
2385 */
2386 if (FontUtilities.isWindows) {
2387 String compatName =
2388 getFontConfiguration().getFallbackFamilyName(name, null);
2389 if (compatName != null) {
2390 font = findFont2D(compatName, style, fallback);
2391 fontNameCache.put(mapName, font);
2392 return font;
2393 }
2394 } else if (lowerCaseName.equals("timesroman")) {
2395 font = findFont2D("serif", style, fallback);
2396 fontNameCache.put(mapName, font);
2397 return font;
2398 } else if (lowerCaseName.equals("helvetica")) {
2399 font = findFont2D("sansserif", style, fallback);
2400 fontNameCache.put(mapName, font);
2401 return font;
2402 } else if (lowerCaseName.equals("courier")) {
2403 font = findFont2D("monospaced", style, fallback);
2404 fontNameCache.put(mapName, font);
2405 return font;
2406 }
2407
2408 if (FontUtilities.isLogging()) {
2409 FontUtilities.getLogger().info("No font found for:" + name);
2410 }
2411
2412 switch (fallback) {
2413 case PHYSICAL_FALLBACK: return getDefaultPhysicalFont();
2414 case LOGICAL_FALLBACK: return getDefaultLogicalFont(style);
2415 default: return null;
2416 }
2417 }
2418
2419 /*
2420 * Workaround for apps which are dependent on a font metrics bug
2421 * in JDK 1.1. This is an unsupported win32 private setting.
2422 * Left in for a customer - do not remove.
2423 */
2424 public boolean usePlatformFontMetrics() {
2425 return usePlatformFontMetrics;
2426 }
2427
2428 public int getNumFonts() {
2429 return physicalFonts.size()+maxCompFont;
2430 }
2431
2432 private static boolean fontSupportsEncoding(Font font, String encoding) {
2433 return FontUtilities.getFont2D(font).supportsEncoding(encoding);
2434 }
2435
2436 protected abstract String getFontPath(boolean noType1Fonts);
2437
2438 private Thread fileCloser = null;
2439 Vector<File> tmpFontFiles = null;
2440
2441 public Font2D createFont2D(File fontFile, int fontFormat,
2442 boolean isCopy, CreatedFontTracker tracker)
2443 throws FontFormatException {
2444
2445 String fontFilePath = fontFile.getPath();
2446 FileFont font2D = null;
2447 final File fFile = fontFile;
2448 final CreatedFontTracker _tracker = tracker;
2449 try {
2450 switch (fontFormat) {
2451 case Font.TRUETYPE_FONT:
2452 font2D = new TrueTypeFont(fontFilePath, null, 0, true);
2453 break;
2454 case Font.TYPE1_FONT:
2455 font2D = new Type1Font(fontFilePath, null, isCopy);
2456 break;
2457 default:
2458 throw new FontFormatException("Unrecognised Font Format");
2459 }
2460 } catch (FontFormatException e) {
2461 if (isCopy) {
2462 java.security.AccessController.doPrivileged(
2463 new java.security.PrivilegedAction() {
2464 public Object run() {
2465 if (_tracker != null) {
2466 _tracker.subBytes((int)fFile.length());
2467 }
2468 fFile.delete();
2469 return null;
2470 }
2471 });
2472 }
2473 throw(e);
2474 }
2475 if (isCopy) {
2476 font2D.setFileToRemove(fontFile, tracker);
2477 synchronized (FontManager.class) {
2478
2479 if (tmpFontFiles == null) {
2480 tmpFontFiles = new Vector<File>();
2481 }
2482 tmpFontFiles.add(fontFile);
2483
2484 if (fileCloser == null) {
2485 final Runnable fileCloserRunnable = new Runnable() {
2486 public void run() {
2487 java.security.AccessController.doPrivileged(
2488 new java.security.PrivilegedAction() {
2489 public Object run() {
2490
2491 for (int i=0;i<CHANNELPOOLSIZE;i++) {
2492 if (fontFileCache[i] != null) {
2493 try {
2494 fontFileCache[i].close();
2495 } catch (Exception e) {
2496 }
2497 }
2498 }
2499 if (tmpFontFiles != null) {
2500 File[] files = new File[tmpFontFiles.size()];
2501 files = tmpFontFiles.toArray(files);
2502 for (int f=0; f<files.length;f++) {
2503 try {
2504 files[f].delete();
2505 } catch (Exception e) {
2506 }
2507 }
2508 }
2509
2510 return null;
2511 }
2512
2513 });
2514 }
2515 };
2516 java.security.AccessController.doPrivileged(
2517 new java.security.PrivilegedAction() {
2518 public Object run() {
2519 /* The thread must be a member of a thread group
2520 * which will not get GCed before VM exit.
2521 * Make its parent the top-level thread group.
2522 */
2523 ThreadGroup tg =
2524 Thread.currentThread().getThreadGroup();
2525 for (ThreadGroup tgn = tg;
2526 tgn != null;
2527 tg = tgn, tgn = tg.getParent());
2528 fileCloser = new Thread(tg, fileCloserRunnable);
2529 fileCloser.setContextClassLoader(null);
2530 Runtime.getRuntime().addShutdownHook(fileCloser);
2531 return null;
2532 }
2533 });
2534 }
2535 }
2536 }
2537 return font2D;
2538 }
2539
2540 /* remind: used in X11GraphicsEnvironment and called often enough
2541 * that we ought to obsolete this code
2542 */
2543 public synchronized String getFullNameByFileName(String fileName) {
2544 PhysicalFont[] physFonts = getPhysicalFonts();
2545 for (int i=0;i<physFonts.length;i++) {
2546 if (physFonts[i].platName.equals(fileName)) {
2547 return (physFonts[i].getFontName(null));
2548 }
2549 }
2550 return null;
2551 }
2552
2553 /*
2554 * This is called when font is determined to be invalid/bad.
2555 * It designed to be called (for example) by the font scaler
2556 * when in processing a font file it is discovered to be incorrect.
2557 * This is different than the case where fonts are discovered to
2558 * be incorrect during initial verification, as such fonts are
2559 * never registered.
2560 * Handles to this font held are re-directed to a default font.
2561 * This default may not be an ideal substitute buts it better than
2562 * crashing This code assumes a PhysicalFont parameter as it doesn't
2563 * make sense for a Composite to be "bad".
2564 */
2565 public synchronized void deRegisterBadFont(Font2D font2D) {
2566 if (!(font2D instanceof PhysicalFont)) {
2567 /* We should never reach here, but just in case */
2568 return;
2569 } else {
2570 if (FontUtilities.isLogging()) {
2571 FontUtilities.getLogger()
2572 .severe("Deregister bad font: " + font2D);
2573 }
2574 replaceFont((PhysicalFont)font2D, getDefaultPhysicalFont());
2575 }
2576 }
2577
2578 /*
2579 * This encapsulates all the work that needs to be done when a
2580 * Font2D is replaced by a different Font2D.
2581 */
2582 public synchronized void replaceFont(PhysicalFont oldFont,
2583 PhysicalFont newFont) {
2584
2585 if (oldFont.handle.font2D != oldFont) {
2586 /* already done */
2587 return;
2588 }
2589
2590 /* If we try to replace the font with itself, that won't work,
2591 * so pick any alternative physical font
2592 */
2593 if (oldFont == newFont) {
2594 if (FontUtilities.isLogging()) {
2595 FontUtilities.getLogger()
2596 .severe("Can't replace bad font with itself " + oldFont);
2597 }
2598 PhysicalFont[] physFonts = getPhysicalFonts();
2599 for (int i=0; i<physFonts.length;i++) {
2600 if (physFonts[i] != newFont) {
2601 newFont = physFonts[i];
2602 break;
2603 }
2604 }
2605 if (oldFont == newFont) {
2606 if (FontUtilities.isLogging()) {
2607 FontUtilities.getLogger()
2608 .severe("This is bad. No good physicalFonts found.");
2609 }
2610 return;
2611 }
2612 }
2613
2614 /* eliminate references to this font, so it won't be located
2615 * by future callers, and will be eligible for GC when all
2616 * references are removed
2617 */
2618 oldFont.handle.font2D = newFont;
2619 physicalFonts.remove(oldFont.fullName);
2620 fullNameToFont.remove(oldFont.fullName.toLowerCase(Locale.ENGLISH));
2621 FontFamily.remove(oldFont);
2622
2623 if (localeFullNamesToFont != null) {
2624 Map.Entry[] mapEntries =
2625 (Map.Entry[])localeFullNamesToFont.entrySet().
2626 toArray(new Map.Entry[0]);
2627 /* Should I be replacing these, or just I just remove
2628 * the names from the map?
2629 */
2630 for (int i=0; i<mapEntries.length;i++) {
2631 if (mapEntries[i].getValue() == oldFont) {
2632 try {
2633 mapEntries[i].setValue(newFont);
2634 } catch (Exception e) {
2635 /* some maps don't support this operation.
2636 * In this case just give up and remove the entry.
2637 */
2638 localeFullNamesToFont.remove(mapEntries[i].getKey());
2639 }
2640 }
2641 }
2642 }
2643
2644 for (int i=0; i<maxCompFont; i++) {
2645 /* Deferred initialization of composites shouldn't be
2646 * a problem for this case, since a font must have been
2647 * initialised to be discovered to be bad.
2648 * Some JRE composites on Solaris use two versions of the same
2649 * font. The replaced font isn't bad, just "smaller" so there's
2650 * no need to make the slot point to the new font.
2651 * Since composites have a direct reference to the Font2D (not
2652 * via a handle) making this substitution is not safe and could
2653 * cause an additional problem and so this substitution is
2654 * warranted only when a font is truly "bad" and could cause
2655 * a crash. So we now replace it only if its being substituted
2656 * with some font other than a fontconfig rank font
2657 * Since in practice a substitution will have the same rank
2658 * this may never happen, but the code is safer even if its
2659 * also now a no-op.
2660 * The only obvious "glitch" from this stems from the current
2661 * implementation that when asked for the number of glyphs in a
2662 * composite it lies and returns the number in slot 0 because
2663 * composite glyphs aren't contiguous. Since we live with that
2664 * we can live with the glitch that depending on how it was
2665 * initialised a composite may return different values for this.
2666 * Fixing the issues with composite glyph ids is tricky as
2667 * there are exclusion ranges and unlike other fonts even the
2668 * true "numGlyphs" isn't a contiguous range. Likely the only
2669 * solution is an API that returns an array of glyph ranges
2670 * which takes precedence over the existing API. That might
2671 * also need to address excluding ranges which represent a
2672 * code point supported by an earlier component.
2673 */
2674 if (newFont.getRank() > Font2D.FONT_CONFIG_RANK) {
2675 compFonts[i].replaceComponentFont(oldFont, newFont);
2676 }
2677 }
2678 }
2679
2680 private synchronized void loadLocaleNames() {
2681 if (localeFullNamesToFont != null) {
2682 return;
2683 }
2684 localeFullNamesToFont = new HashMap<String, TrueTypeFont>();
2685 Font2D[] fonts = getRegisteredFonts();
2686 for (int i=0; i<fonts.length; i++) {
2687 if (fonts[i] instanceof TrueTypeFont) {
2688 TrueTypeFont ttf = (TrueTypeFont)fonts[i];
2689 String[] fullNames = ttf.getAllFullNames();
2690 for (int n=0; n<fullNames.length; n++) {
2691 localeFullNamesToFont.put(fullNames[n], ttf);
2692 }
2693 FontFamily family = FontFamily.getFamily(ttf.familyName);
2694 if (family != null) {
2695 FontFamily.addLocaleNames(family, ttf.getAllFamilyNames());
2696 }
2697 }
2698 }
2699 }
2700
2701 /* This replicate the core logic of findFont2D but operates on
2702 * all the locale names. This hasn't been merged into findFont2D to
2703 * keep the logic simpler and reduce overhead, since this case is
2704 * almost never used. The main case in which it is called is when
2705 * a bogus font name is used and we need to check all possible names
2706 * before returning the default case.
2707 */
2708 private Font2D findFont2DAllLocales(String name, int style) {
2709
2710 if (FontUtilities.isLogging()) {
2711 FontUtilities.getLogger()
2712 .info("Searching localised font names for:" + name);
2713 }
2714
2715 /* If reach here and no match has been located, then if we have
2716 * not yet built the map of localeFullNamesToFont for TT fonts, do so
2717 * now. This method must be called after all fonts have been loaded.
2718 */
2719 if (localeFullNamesToFont == null) {
2720 loadLocaleNames();
2721 }
2722 String lowerCaseName = name.toLowerCase();
2723 Font2D font = null;
2724
2725 /* First see if its a family name. */
2726 FontFamily family = FontFamily.getLocaleFamily(lowerCaseName);
2727 if (family != null) {
2728 font = family.getFont(style);
2729 if (font == null) {
2730 font = family.getClosestStyle(style);
2731 }
2732 if (font != null) {
2733 return font;
2734 }
2735 }
2736
2737 /* If it wasn't a family name, it should be a full name. */
2738 synchronized (this) {
2739 font = localeFullNamesToFont.get(name);
2740 }
2741 if (font != null) {
2742 if (font.style == style || style == Font.PLAIN) {
2743 return font;
2744 } else {
2745 family = FontFamily.getFamily(font.getFamilyName(null));
2746 if (family != null) {
2747 Font2D familyFont = family.getFont(style);
2748 /* We exactly matched the requested style, use it! */
2749 if (familyFont != null) {
2750 return familyFont;
2751 } else {
2752 familyFont = family.getClosestStyle(style);
2753 if (familyFont != null) {
2754 /* The next check is perhaps one
2755 * that shouldn't be done. ie if we get this
2756 * far we have probably as close a match as we
2757 * are going to get. We could load all fonts to
2758 * see if somehow some parts of the family are
2759 * loaded but not all of it.
2760 * This check is commented out for now.
2761 */
2762 if (!familyFont.canDoStyle(style)) {
2763 familyFont = null;
2764 }
2765 return familyFont;
2766 }
2767 }
2768 }
2769 }
2770 }
2771 return font;
2772 }
2773
2774 /* Supporting "alternate" composite fonts on 2D graphics objects
2775 * is accessed by the application by calling methods on the local
2776 * GraphicsEnvironment. The overall implementation is described
2777 * in one place, here, since otherwise the implementation is spread
2778 * around it may be difficult to track.
2779 * The methods below call into SunGraphicsEnvironment which creates a
2780 * new FontConfiguration instance. The FontConfiguration class,
2781 * and its platform sub-classes are updated to take parameters requesting
2782 * these behaviours. This is then used to create new composite font
2783 * instances. Since this calls the initCompositeFont method in
2784 * SunGraphicsEnvironment it performs the same initialization as is
2785 * performed normally. There may be some duplication of effort, but
2786 * that code is already written to be able to perform properly if called
2787 * to duplicate work. The main difference is that if we detect we are
2788 * running in an applet/browser/Java plugin environment these new fonts
2789 * are not placed in the "default" maps but into an AppContext instance.
2790 * The font lookup mechanism in java.awt.Font.getFont2D() is also updated
2791 * so that look-up for composite fonts will in that case always
2792 * do a lookup rather than returning a cached result.
2793 * This is inefficient but necessary else singleton java.awt.Font
2794 * instances would not retrieve the correct Font2D for the appcontext.
2795 * sun.font.FontManager.findFont2D is also updated to that it uses
2796 * a name map cache specific to that appcontext.
2797 *
2798 * Getting an AppContext is expensive, so there is a global variable
2799 * that records whether these methods have ever been called and can
2800 * avoid the expense for almost all applications. Once the correct
2801 * CompositeFont is associated with the Font, everything should work
2802 * through existing mechanisms.
2803 * A special case is that GraphicsEnvironment.getAllFonts() must
2804 * return an AppContext specific list.
2805 *
2806 * Calling the methods below is "heavyweight" but it is expected that
2807 * these methods will be called very rarely.
2808 *
2809 * If _usingPerAppContextComposites is true, we are in "applet"
2810 * (eg browser) enviroment and at least one context has selected
2811 * an alternate composite font behaviour.
2812 * If _usingAlternateComposites is true, we are not in an "applet"
2813 * environment and the (single) application has selected
2814 * an alternate composite font behaviour.
2815 *
2816 * - Printing: The implementation delegates logical fonts to an AWT
2817 * mechanism which cannot use these alternate configurations.
2818 * We can detect that alternate fonts are in use and back-off to 2D, but
2819 * that uses outlines. Much of this can be fixed with additional work
2820 * but that may have to wait. The results should be correct, just not
2821 * optimal.
2822 */
2823 private static final Object altJAFontKey = new Object();
2824 private static final Object localeFontKey = new Object();
2825 private static final Object proportionalFontKey = new Object();
2826 private boolean _usingPerAppContextComposites = false;
2827 private boolean _usingAlternateComposites = false;
2828
2829 /* These values are used only if we are running as a standalone
2830 * application, as determined by maybeMultiAppContext();
2831 */
2832 private static boolean gAltJAFont = false;
2833 private boolean gLocalePref = false;
2834 private boolean gPropPref = false;
2835
2836 /* This method doesn't check if alternates are selected in this app
2837 * context. Its used by the FontMetrics caching code which in such
2838 * a case cannot retrieve a cached metrics solely on the basis of
2839 * the Font.equals() method since it needs to also check if the Font2D
2840 * is the same.
2841 * We also use non-standard composites for Swing native L&F fonts on
2842 * Windows. In that case the policy is that the metrics reported are
2843 * based solely on the physical font in the first slot which is the
2844 * visible java.awt.Font. So in that case the metrics cache which tests
2845 * the Font does what we want. In the near future when we expand the GTK
2846 * logical font definitions we may need to revisit this if GTK reports
2847 * combined metrics instead. For now though this test can be simple.
2848 */
2849 public boolean maybeUsingAlternateCompositeFonts() {
2850 return _usingAlternateComposites || _usingPerAppContextComposites;
2851 }
2852
2853 public boolean usingAlternateCompositeFonts() {
2854 return (_usingAlternateComposites ||
2855 (_usingPerAppContextComposites &&
2856 AppContext.getAppContext().get(CompositeFont.class) != null));
2857 }
2858
2859 private static boolean maybeMultiAppContext() {
2860 Boolean appletSM = (Boolean)
2861 java.security.AccessController.doPrivileged(
2862 new java.security.PrivilegedAction() {
2863 public Object run() {
2864 SecurityManager sm = System.getSecurityManager();
2865 return new Boolean
2866 (sm instanceof sun.applet.AppletSecurity);
2867 }
2868 });
2869 return appletSM.booleanValue();
2870 }
2871
2872 /* Modifies the behaviour of a subsequent call to preferLocaleFonts()
2873 * to use Mincho instead of Gothic for dialoginput in JA locales
2874 * on windows. Not needed on other platforms.
2875 */
2876 public synchronized void useAlternateFontforJALocales() {
2877 if (FontUtilities.isLogging()) {
2878 FontUtilities.getLogger()
2879 .info("Entered useAlternateFontforJALocales().");
2880 }
2881 if (!FontUtilities.isWindows) {
2882 return;
2883 }
2884
2885 if (!maybeMultiAppContext()) {
2886 gAltJAFont = true;
2887 } else {
2888 AppContext appContext = AppContext.getAppContext();
2889 appContext.put(altJAFontKey, altJAFontKey);
2890 }
2891 }
2892
2893 public boolean usingAlternateFontforJALocales() {
2894 if (!maybeMultiAppContext()) {
2895 return gAltJAFont;
2896 } else {
2897 AppContext appContext = AppContext.getAppContext();
2898 return appContext.get(altJAFontKey) == altJAFontKey;
2899 }
2900 }
2901
2902 public synchronized void preferLocaleFonts() {
2903 if (FontUtilities.isLogging()) {
2904 FontUtilities.getLogger().info("Entered preferLocaleFonts().");
2905 }
2906 /* Test if re-ordering will have any effect */
2907 if (!FontConfiguration.willReorderForStartupLocale()) {
2908 return;
2909 }
2910
2911 if (!maybeMultiAppContext()) {
2912 if (gLocalePref == true) {
2913 return;
2914 }
2915 gLocalePref = true;
2916 createCompositeFonts(fontNameCache, gLocalePref, gPropPref);
2917 _usingAlternateComposites = true;
2918 } else {
2919 AppContext appContext = AppContext.getAppContext();
2920 if (appContext.get(localeFontKey) == localeFontKey) {
2921 return;
2922 }
2923 appContext.put(localeFontKey, localeFontKey);
2924 boolean acPropPref =
2925 appContext.get(proportionalFontKey) == proportionalFontKey;
2926 ConcurrentHashMap<String, Font2D>
2927 altNameCache = new ConcurrentHashMap<String, Font2D> ();
2928 /* If there is an existing hashtable, we can drop it. */
2929 appContext.put(CompositeFont.class, altNameCache);
2930 _usingPerAppContextComposites = true;
2931 createCompositeFonts(altNameCache, true, acPropPref);
2932 }
2933 }
2934
2935 public synchronized void preferProportionalFonts() {
2936 if (FontUtilities.isLogging()) {
2937 FontUtilities.getLogger()
2938 .info("Entered preferProportionalFonts().");
2939 }
2940 /* If no proportional fonts are configured, there's no need
2941 * to take any action.
2942 */
2943 if (!FontConfiguration.hasMonoToPropMap()) {
2944 return;
2945 }
2946
2947 if (!maybeMultiAppContext()) {
2948 if (gPropPref == true) {
2949 return;
2950 }
2951 gPropPref = true;
2952 createCompositeFonts(fontNameCache, gLocalePref, gPropPref);
2953 _usingAlternateComposites = true;
2954 } else {
2955 AppContext appContext = AppContext.getAppContext();
2956 if (appContext.get(proportionalFontKey) == proportionalFontKey) {
2957 return;
2958 }
2959 appContext.put(proportionalFontKey, proportionalFontKey);
2960 boolean acLocalePref =
2961 appContext.get(localeFontKey) == localeFontKey;
2962 ConcurrentHashMap<String, Font2D>
2963 altNameCache = new ConcurrentHashMap<String, Font2D> ();
2964 /* If there is an existing hashtable, we can drop it. */
2965 appContext.put(CompositeFont.class, altNameCache);
2966 _usingPerAppContextComposites = true;
2967 createCompositeFonts(altNameCache, acLocalePref, true);
2968 }
2969 }
2970
2971 private static HashSet<String> installedNames = null;
2972 private static HashSet<String> getInstalledNames() {
2973 if (installedNames == null) {
2974 Locale l = getSystemStartupLocale();
2975 SunFontManager fontManager = SunFontManager.getInstance();
2976 String[] installedFamilies =
2977 fontManager.getInstalledFontFamilyNames(l);
2978 Font[] installedFonts = fontManager.getAllInstalledFonts();
2979 HashSet<String> names = new HashSet<String>();
2980 for (int i=0; i<installedFamilies.length; i++) {
2981 names.add(installedFamilies[i].toLowerCase(l));
2982 }
2983 for (int i=0; i<installedFonts.length; i++) {
2984 names.add(installedFonts[i].getFontName(l).toLowerCase(l));
2985 }
2986 installedNames = names;
2987 }
2988 return installedNames;
2989 }
2990
2991 /* Keys are used to lookup per-AppContext Hashtables */
2992 private static final Object regFamilyKey = new Object();
2993 private static final Object regFullNameKey = new Object();
2994 private Hashtable<String,FontFamily> createdByFamilyName;
2995 private Hashtable<String,Font2D> createdByFullName;
2996 private boolean fontsAreRegistered = false;
2997 private boolean fontsAreRegisteredPerAppContext = false;
2998
2999 public boolean registerFont(Font font) {
3000 /* This method should not be called with "null".
3001 * It is the caller's responsibility to ensure that.
3002 */
3003 if (font == null) {
3004 return false;
3005 }
3006
3007 /* Initialise these objects only once we start to use this API */
3008 synchronized (regFamilyKey) {
3009 if (createdByFamilyName == null) {
3010 createdByFamilyName = new Hashtable<String,FontFamily>();
3011 createdByFullName = new Hashtable<String,Font2D>();
3012 }
3013 }
3014
3015 if (! FontAccess.getFontAccess().isCreatedFont(font)) {
3016 return false;
3017 }
3018 /* We want to ensure that this font cannot override existing
3019 * installed fonts. Check these conditions :
3020 * - family name is not that of an installed font
3021 * - full name is not that of an installed font
3022 * - family name is not the same as the full name of an installed font
3023 * - full name is not the same as the family name of an installed font
3024 * The last two of these may initially look odd but the reason is
3025 * that (unfortunately) Font constructors do not distinuguish these.
3026 * An extreme example of such a problem would be a font which has
3027 * family name "Dialog.Plain" and full name of "Dialog".
3028 * The one arguably overly stringent restriction here is that if an
3029 * application wants to supply a new member of an existing family
3030 * It will get rejected. But since the JRE can perform synthetic
3031 * styling in many cases its not necessary.
3032 * We don't apply the same logic to registered fonts. If apps want
3033 * to do this lets assume they have a reason. It won't cause problems
3034 * except for themselves.
3035 */
3036 HashSet<String> names = getInstalledNames();
3037 Locale l = getSystemStartupLocale();
3038 String familyName = font.getFamily(l).toLowerCase();
3039 String fullName = font.getFontName(l).toLowerCase();
3040 if (names.contains(familyName) || names.contains(fullName)) {
3041 return false;
3042 }
3043
3044 /* Checks passed, now register the font */
3045 Hashtable<String,FontFamily> familyTable;
3046 Hashtable<String,Font2D> fullNameTable;
3047 if (!maybeMultiAppContext()) {
3048 familyTable = createdByFamilyName;
3049 fullNameTable = createdByFullName;
3050 fontsAreRegistered = true;
3051 } else {
3052 AppContext appContext = AppContext.getAppContext();
3053 familyTable =
3054 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
3055 fullNameTable =
3056 (Hashtable<String,Font2D>)appContext.get(regFullNameKey);
3057 if (familyTable == null) {
3058 familyTable = new Hashtable<String,FontFamily>();
3059 fullNameTable = new Hashtable<String,Font2D>();
3060 appContext.put(regFamilyKey, familyTable);
3061 appContext.put(regFullNameKey, fullNameTable);
3062 }
3063 fontsAreRegisteredPerAppContext = true;
3064 }
3065 /* Create the FontFamily and add font to the tables */
3066 Font2D font2D = FontUtilities.getFont2D(font);
3067 int style = font2D.getStyle();
3068 FontFamily family = familyTable.get(familyName);
3069 if (family == null) {
3070 family = new FontFamily(font.getFamily(l));
3071 familyTable.put(familyName, family);
3072 }
3073 /* Remove name cache entries if not using app contexts.
3074 * To accommodate a case where code may have registered first a plain
3075 * family member and then used it and is now registering a bold family
3076 * member, we need to remove all members of the family, so that the
3077 * new style can get picked up rather than continuing to synthesise.
3078 */
3079 if (fontsAreRegistered) {
3080 removeFromCache(family.getFont(Font.PLAIN));
3081 removeFromCache(family.getFont(Font.BOLD));
3082 removeFromCache(family.getFont(Font.ITALIC));
3083 removeFromCache(family.getFont(Font.BOLD|Font.ITALIC));
3084 removeFromCache(fullNameTable.get(fullName));
3085 }
3086 family.setFont(font2D, style);
3087 fullNameTable.put(fullName, font2D);
3088 return true;
3089 }
3090
3091 /* Remove from the name cache all references to the Font2D */
3092 private void removeFromCache(Font2D font) {
3093 if (font == null) {
3094 return;
3095 }
3096 String[] keys = (String[])(fontNameCache.keySet().toArray(STR_ARRAY));
3097 for (int k=0; k<keys.length;k++) {
3098 if (fontNameCache.get(keys[k]) == font) {
3099 fontNameCache.remove(keys[k]);
3100 }
3101 }
3102 }
3103
3104 // It may look odd to use TreeMap but its more convenient to the caller.
3105 public TreeMap<String, String> getCreatedFontFamilyNames() {
3106
3107 Hashtable<String,FontFamily> familyTable;
3108 if (fontsAreRegistered) {
3109 familyTable = createdByFamilyName;
3110 } else if (fontsAreRegisteredPerAppContext) {
3111 AppContext appContext = AppContext.getAppContext();
3112 familyTable =
3113 (Hashtable<String,FontFamily>)appContext.get(regFamilyKey);
3114 } else {
3115 return null;
3116 }
3117
3118 Locale l = getSystemStartupLocale();
3119 synchronized (familyTable) {
3120 TreeMap<String, String> map = new TreeMap<String, String>();
3121 for (FontFamily f : familyTable.values()) {
3122 Font2D font2D = f.getFont(Font.PLAIN);
3123 if (font2D == null) {
3124 font2D = f.getClosestStyle(Font.PLAIN);
3125 }
3126 String name = font2D.getFamilyName(l);
3127 map.put(name.toLowerCase(l), name);
3128 }
3129 return map;
3130 }
3131 }
3132
3133 public Font[] getCreatedFonts() {
3134
3135 Hashtable<String,Font2D> nameTable;
3136 if (fontsAreRegistered) {
3137 nameTable = createdByFullName;
3138 } else if (fontsAreRegisteredPerAppContext) {
3139 AppContext appContext = AppContext.getAppContext();
3140 nameTable =
3141 (Hashtable<String,Font2D>)appContext.get(regFullNameKey);
3142 } else {
3143 return null;
3144 }
3145
3146 Locale l = getSystemStartupLocale();
3147 synchronized (nameTable) {
3148 Font[] fonts = new Font[nameTable.size()];
3149 int i=0;
3150 for (Font2D font2D : nameTable.values()) {
3151 fonts[i++] = new Font(font2D.getFontName(l), Font.PLAIN, 1);
3152 }
3153 return fonts;
3154 }
3155 }
3156
3157
3158 protected String[] getPlatformFontDirs(boolean noType1Fonts) {
3159
3160 /* First check if we already initialised path dirs */
3161 if (pathDirs != null) {
3162 return pathDirs;
3163 }
3164
3165 String path = getPlatformFontPath(noType1Fonts);
3166 StringTokenizer parser =
3167 new StringTokenizer(path, File.pathSeparator);
3168 ArrayList<String> pathList = new ArrayList<String>();
3169 try {
3170 while (parser.hasMoreTokens()) {
3171 pathList.add(parser.nextToken());
3172 }
3173 } catch (NoSuchElementException e) {
3174 }
3175 pathDirs = pathList.toArray(new String[0]);
3176 return pathDirs;
3177 }
3178
3179 /**
3180 * Returns an array of two strings. The first element is the
3181 * name of the font. The second element is the file name.
3182 */
3183 public abstract String[] getDefaultPlatformFont();
3184
3185 // Begin: Refactored from SunGraphicsEnviroment.
3186
3187 /*
3188 * helper function for registerFonts
3189 */
3190 private void addDirFonts(String dirName, File dirFile,
3191 FilenameFilter filter,
3192 int fontFormat, boolean useJavaRasterizer,
3193 int fontRank,
3194 boolean defer, boolean resolveSymLinks) {
3195 String[] ls = dirFile.list(filter);
3196 if (ls == null || ls.length == 0) {
3197 return;
3198 }
3199 String[] fontNames = new String[ls.length];
3200 String[][] nativeNames = new String[ls.length][];
3201 int fontCount = 0;
3202
3203 for (int i=0; i < ls.length; i++ ) {
3204 File theFile = new File(dirFile, ls[i]);
3205 String fullName = null;
3206 if (resolveSymLinks) {
3207 try {
3208 fullName = theFile.getCanonicalPath();
3209 } catch (IOException e) {
3210 }
3211 }
3212 if (fullName == null) {
3213 fullName = dirName + File.separator + ls[i];
3214 }
3215
3216 // REMIND: case compare depends on platform
3217 if (registeredFontFiles.contains(fullName)) {
3218 continue;
3219 }
3220
3221 if (badFonts != null && badFonts.contains(fullName)) {
3222 if (FontUtilities.debugFonts()) {
3223 FontUtilities.getLogger()
3224 .warning("skip bad font " + fullName);
3225 }
3226 continue; // skip this font file.
3227 }
3228
3229 registeredFontFiles.add(fullName);
3230
3231 if (FontUtilities.debugFonts()
3232 && FontUtilities.getLogger().isLoggable(PlatformLogger.INFO)) {
3233 String message = "Registering font " + fullName;
3234 String[] natNames = getNativeNames(fullName, null);
3235 if (natNames == null) {
3236 message += " with no native name";
3237 } else {
3238 message += " with native name(s) " + natNames[0];
3239 for (int nn = 1; nn < natNames.length; nn++) {
3240 message += ", " + natNames[nn];
3241 }
3242 }
3243 FontUtilities.getLogger().info(message);
3244 }
3245 fontNames[fontCount] = fullName;
3246 nativeNames[fontCount++] = getNativeNames(fullName, null);
3247 }
3248 registerFonts(fontNames, nativeNames, fontCount, fontFormat,
3249 useJavaRasterizer, fontRank, defer);
3250 return;
3251 }
3252
3253 protected String[] getNativeNames(String fontFileName,
3254 String platformName) {
3255 return null;
3256 }
3257
3258 /**
3259 * Returns a file name for the physical font represented by this platform
3260 * font name. The default implementation tries to obtain the file name
3261 * from the font configuration.
3262 * Subclasses may override to provide information from other sources.
3263 */
3264 protected String getFileNameFromPlatformName(String platformFontName) {
3265 return fontConfig.getFileNameFromPlatformName(platformFontName);
3266 }
3267
3268 /**
3269 * Return the default font configuration.
3270 */
3271 public FontConfiguration getFontConfiguration() {
3272 return fontConfig;
3273 }
3274
3275 /* A call to this method should be followed by a call to
3276 * registerFontDirs(..)
3277 */
3278 public String getPlatformFontPath(boolean noType1Font) {
3279 if (fontPath == null) {
3280 fontPath = getFontPath(noType1Font);
3281 }
3282 return fontPath;
3283 }
3284
3285 public static boolean isOpenJDK() {
3286 return FontUtilities.isOpenJDK;
3287 }
3288
3289 protected void loadFonts() {
3290 if (discoveredAllFonts) {
3291 return;
3292 }
3293 /* Use lock specific to the font system */
3294 synchronized (this) {
3295 if (FontUtilities.debugFonts()) {
3296 Thread.dumpStack();
3297 FontUtilities.getLogger()
3298 .info("SunGraphicsEnvironment.loadFonts() called");
3299 }
3300 initialiseDeferredFonts();
3301
3302 java.security.AccessController.doPrivileged(
3303 new java.security.PrivilegedAction() {
3304 public Object run() {
3305 if (fontPath == null) {
3306 fontPath = getPlatformFontPath(noType1Font);
3307 registerFontDirs(fontPath);
3308 }
3309 if (fontPath != null) {
3310 // this will find all fonts including those already
3311 // registered. But we have checks in place to prevent
3312 // double registration.
3313 if (! gotFontsFromPlatform()) {
3314 registerFontsOnPath(fontPath, false,
3315 Font2D.UNKNOWN_RANK,
3316 false, true);
3317 loadedAllFontFiles = true;
3318 }
3319 }
3320 registerOtherFontFiles(registeredFontFiles);
3321 discoveredAllFonts = true;
3322 return null;
3323 }
3324 });
3325 }
3326 }
3327
3328 protected void registerFontDirs(String pathName) {
3329 return;
3330 }
3331
3332 private void registerFontsOnPath(String pathName,
3333 boolean useJavaRasterizer, int fontRank,
3334 boolean defer, boolean resolveSymLinks) {
3335
3336 StringTokenizer parser = new StringTokenizer(pathName,
3337 File.pathSeparator);
3338 try {
3339 while (parser.hasMoreTokens()) {
3340 registerFontsInDir(parser.nextToken(),
3341 useJavaRasterizer, fontRank,
3342 defer, resolveSymLinks);
3343 }
3344 } catch (NoSuchElementException e) {
3345 }
3346 }
3347
3348 /* Called to register fall back fonts */
3349 public void registerFontsInDir(String dirName) {
3350 registerFontsInDir(dirName, true, Font2D.JRE_RANK, true, false);
3351 }
3352
3353 private void registerFontsInDir(String dirName, boolean useJavaRasterizer,
3354 int fontRank,
3355 boolean defer, boolean resolveSymLinks) {
3356 File pathFile = new File(dirName);
3357 addDirFonts(dirName, pathFile, ttFilter,
3358 FONTFORMAT_TRUETYPE, useJavaRasterizer,
3359 fontRank==Font2D.UNKNOWN_RANK ?
3360 Font2D.TTF_RANK : fontRank,
3361 defer, resolveSymLinks);
3362 addDirFonts(dirName, pathFile, t1Filter,
3363 FONTFORMAT_TYPE1, useJavaRasterizer,
3364 fontRank==Font2D.UNKNOWN_RANK ?
3365 Font2D.TYPE1_RANK : fontRank,
3366 defer, resolveSymLinks);
3367 }
3368
3369 protected void registerFontDir(String path) {
3370 }
3371
3372 /**
3373 * Returns file name for default font, either absolute
3374 * or relative as needed by registerFontFile.
3375 */
3376 public synchronized String getDefaultFontFile() {
3377 if (defaultFontFileName == null) {
3378 initDefaultFonts();
3379 }
3380 return defaultFontFileName;
3381 }
3382
3383 private void initDefaultFonts() {
3384 if (!isOpenJDK()) {
3385 defaultFontName = lucidaFontName;
3386 if (useAbsoluteFontFileNames()) {
3387 defaultFontFileName =
3388 jreFontDirName + File.separator + FontUtilities.LUCIDA_FILE_NAME;
3389 } else {
3390 defaultFontFileName = FontUtilities.LUCIDA_FILE_NAME;
3391 }
3392 }
3393 }
3394
3395 /**
3396 * Whether registerFontFile expects absolute or relative
3397 * font file names.
3398 */
3399 protected boolean useAbsoluteFontFileNames() {
3400 return true;
3401 }
3402
3403 /**
3404 * Creates this environment's FontConfiguration.
3405 */
3406 protected abstract FontConfiguration createFontConfiguration();
3407
3408 public abstract FontConfiguration
3409 createFontConfiguration(boolean preferLocaleFonts,
3410 boolean preferPropFonts);
3411
3412 /**
3413 * Returns face name for default font, or null if
3414 * no face names are used for CompositeFontDescriptors
3415 * for this platform.
3416 */
3417 public synchronized String getDefaultFontFaceName() {
3418 if (defaultFontName == null) {
3419 initDefaultFonts();
3420 }
3421 return defaultFontName;
3422 }
3423
3424 public void loadFontFiles() {
3425 loadFonts();
3426 if (loadedAllFontFiles) {
3427 return;
3428 }
3429 /* Use lock specific to the font system */
3430 synchronized (this) {
3431 if (FontUtilities.debugFonts()) {
3432 Thread.dumpStack();
3433 FontUtilities.getLogger().info("loadAllFontFiles() called");
3434 }
3435 java.security.AccessController.doPrivileged(
3436 new java.security.PrivilegedAction() {
3437 public Object run() {
3438 if (fontPath == null) {
3439 fontPath = getPlatformFontPath(noType1Font);
3440 }
3441 if (fontPath != null) {
3442 // this will find all fonts including those already
3443 // registered. But we have checks in place to prevent
3444 // double registration.
3445 registerFontsOnPath(fontPath, false,
3446 Font2D.UNKNOWN_RANK,
3447 false, true);
3448 }
3449 loadedAllFontFiles = true;
3450 return null;
3451 }
3452 });
3453 }
3454 }
3455
3456 /*
3457 * This method asks the font configuration API for all platform names
3458 * used as components of composite/logical fonts and iterates over these
3459 * looking up their corresponding file name and registers these fonts.
3460 * It also ensures that the fonts are accessible via platform APIs.
3461 * The composites themselves are then registered.
3462 */
3463 private void
3464 initCompositeFonts(FontConfiguration fontConfig,
3465 ConcurrentHashMap<String, Font2D> altNameCache) {
3466
3467 if (FontUtilities.isLogging()) {
3468 FontUtilities.getLogger()
3469 .info("Initialising composite fonts");
3470 }
3471
3472 int numCoreFonts = fontConfig.getNumberCoreFonts();
3473 String[] fcFonts = fontConfig.getPlatformFontNames();
3474 for (int f=0; f<fcFonts.length; f++) {
3475 String platformFontName = fcFonts[f];
3476 String fontFileName =
3477 getFileNameFromPlatformName(platformFontName);
3478 String[] nativeNames = null;
3479 if (fontFileName == null
3480 || fontFileName.equals(platformFontName)) {
3481 /* No file located, so register using the platform name,
3482 * i.e. as a native font.
3483 */
3484 fontFileName = platformFontName;
3485 } else {
3486 if (f < numCoreFonts) {
3487 /* If platform APIs also need to access the font, add it
3488 * to a set to be registered with the platform too.
3489 * This may be used to add the parent directory to the X11
3490 * font path if its not already there. See the docs for the
3491 * subclass implementation.
3492 * This is now mainly for the benefit of X11-based AWT
3493 * But for historical reasons, 2D initialisation code
3494 * makes these calls.
3495 * If the fontconfiguration file is properly set up
3496 * so that all fonts are mapped to files and all their
3497 * appropriate directories are specified, then this
3498 * method will be low cost as it will return after
3499 * a test that finds a null lookup map.
3500 */
3501 addFontToPlatformFontPath(platformFontName);
3502 }
3503 nativeNames = getNativeNames(fontFileName, platformFontName);
3504 }
3505 /* Uncomment these two lines to "generate" the XLFD->filename
3506 * mappings needed to speed start-up on Solaris.
3507 * Augment this with the appendedpathname and the mappings
3508 * for native (F3) fonts
3509 */
3510 //String platName = platformFontName.replaceAll(" ", "_");
3511 //System.out.println("filename."+platName+"="+fontFileName);
3512 registerFontFile(fontFileName, nativeNames,
3513 Font2D.FONT_CONFIG_RANK, true);
3514
3515
3516 }
3517 /* This registers accumulated paths from the calls to
3518 * addFontToPlatformFontPath(..) and any specified by
3519 * the font configuration. Rather than registering
3520 * the fonts it puts them in a place and form suitable for
3521 * the Toolkit to pick up and use if a toolkit is initialised,
3522 * and if it uses X11 fonts.
3523 */
3524 registerPlatformFontsUsedByFontConfiguration();
3525
3526 CompositeFontDescriptor[] compositeFontInfo
3527 = fontConfig.get2DCompositeFontInfo();
3528 for (int i = 0; i < compositeFontInfo.length; i++) {
3529 CompositeFontDescriptor descriptor = compositeFontInfo[i];
3530 String[] componentFileNames = descriptor.getComponentFileNames();
3531 String[] componentFaceNames = descriptor.getComponentFaceNames();
3532
3533 /* It would be better eventually to handle this in the
3534 * FontConfiguration code which should also remove duplicate slots
3535 */
3536 if (missingFontFiles != null) {
3537 for (int ii=0; ii<componentFileNames.length; ii++) {
3538 if (missingFontFiles.contains(componentFileNames[ii])) {
3539 componentFileNames[ii] = getDefaultFontFile();
3540 componentFaceNames[ii] = getDefaultFontFaceName();
3541 }
3542 }
3543 }
3544
3545 /* FontConfiguration needs to convey how many fonts it has added
3546 * as fallback component fonts which should not affect metrics.
3547 * The core component count will be the number of metrics slots.
3548 * This does not preclude other mechanisms for adding
3549 * fall back component fonts to the composite.
3550 */
3551 if (altNameCache != null) {
3552 SunFontManager.registerCompositeFont(
3553 descriptor.getFaceName(),
3554 componentFileNames, componentFaceNames,
3555 descriptor.getCoreComponentCount(),
3556 descriptor.getExclusionRanges(),
3557 descriptor.getExclusionRangeLimits(),
3558 true,
3559 altNameCache);
3560 } else {
3561 registerCompositeFont(descriptor.getFaceName(),
3562 componentFileNames, componentFaceNames,
3563 descriptor.getCoreComponentCount(),
3564 descriptor.getExclusionRanges(),
3565 descriptor.getExclusionRangeLimits(),
3566 true);
3567 }
3568 if (FontUtilities.debugFonts()) {
3569 FontUtilities.getLogger()
3570 .info("registered " + descriptor.getFaceName());
3571 }
3572 }
3573 }
3574
3575 /**
3576 * Notifies graphics environment that the logical font configuration
3577 * uses the given platform font name. The graphics environment may
3578 * use this for platform specific initialization.
3579 */
3580 protected void addFontToPlatformFontPath(String platformFontName) {
3581 }
3582
3583 protected void registerFontFile(String fontFileName, String[] nativeNames,
3584 int fontRank, boolean defer) {
3585 // REMIND: case compare depends on platform
3586 if (registeredFontFiles.contains(fontFileName)) {
3587 return;
3588 }
3589 int fontFormat;
3590 if (ttFilter.accept(null, fontFileName)) {
3591 fontFormat = FONTFORMAT_TRUETYPE;
3592 } else if (t1Filter.accept(null, fontFileName)) {
3593 fontFormat = FONTFORMAT_TYPE1;
3594 } else {
3595 fontFormat = FONTFORMAT_NATIVE;
3596 }
3597 registeredFontFiles.add(fontFileName);
3598 if (defer) {
3599 registerDeferredFont(fontFileName, fontFileName, nativeNames,
3600 fontFormat, false, fontRank);
3601 } else {
3602 registerFontFile(fontFileName, nativeNames, fontFormat, false,
3603 fontRank);
3604 }
3605 }
3606
3607 protected void registerPlatformFontsUsedByFontConfiguration() {
3608 }
3609
3610 /*
3611 * A GE may verify whether a font file used in a fontconfiguration
3612 * exists. If it doesn't then either we may substitute the default
3613 * font, or perhaps elide it altogether from the composite font.
3614 * This makes some sense on windows where the font file is only
3615 * likely to be in one place. But on other OSes, eg Linux, the file
3616 * can move around depending. So there we probably don't want to assume
3617 * its missing and so won't add it to this list.
3618 * If this list - missingFontFiles - is non-null then the composite
3619 * font initialisation logic tests to see if a font file is in that
3620 * set.
3621 * Only one thread should be able to add to this set so we don't
3622 * synchronize.
3623 */
3624 protected void addToMissingFontFileList(String fileName) {
3625 if (missingFontFiles == null) {
3626 missingFontFiles = new HashSet<String>();
3627 }
3628 missingFontFiles.add(fileName);
3629 }
3630
3631 /*
3632 * This is for use only within getAllFonts().
3633 * Fonts listed in the fontconfig files for windows were all
3634 * on the "deferred" initialisation list. They were registered
3635 * either in the course of the application, or in the call to
3636 * loadFonts() within getAllFonts(). The fontconfig file specifies
3637 * the names of the fonts using the English names. If there's a
3638 * different name in the execution locale, then the platform will
3639 * report that, and we will construct the font with both names, and
3640 * thereby enumerate it twice. This happens for Japanese fonts listed
3641 * in the windows fontconfig, when run in the JA locale. The solution
3642 * is to rely (in this case) on the platform's font->file mapping to
3643 * determine that this name corresponds to a file we already registered.
3644 * This works because
3645 * - we know when we get here all deferred fonts are already initialised
3646 * - when we register a font file, we register all fonts in it.
3647 * - we know the fontconfig fonts are all in the windows registry
3648 */
3649 private boolean isNameForRegisteredFile(String fontName) {
3650 String fileName = getFileNameForFontName(fontName);
3651 if (fileName == null) {
3652 return false;
3653 }
3654 return registeredFontFiles.contains(fileName);
3655 }
3656
3657 /*
3658 * This invocation is not in a privileged block because
3659 * all privileged operations (reading files and properties)
3660 * was conducted on the creation of the GE
3661 */
3662 public void
3663 createCompositeFonts(ConcurrentHashMap<String, Font2D> altNameCache,
3664 boolean preferLocale,
3665 boolean preferProportional) {
3666
3667 FontConfiguration fontConfig =
3668 createFontConfiguration(preferLocale, preferProportional);
3669 initCompositeFonts(fontConfig, altNameCache);
3670 }
3671
3672 /**
3673 * Returns all fonts installed in this environment.
3674 */
3675 public Font[] getAllInstalledFonts() {
3676 if (allFonts == null) {
3677 loadFonts();
3678 TreeMap fontMapNames = new TreeMap();
3679 /* warning: the number of composite fonts could change dynamically
3680 * if applications are allowed to create them. "allfonts" could
3681 * then be stale.
3682 */
3683 Font2D[] allfonts = getRegisteredFonts();
3684 for (int i=0; i < allfonts.length; i++) {
3685 if (!(allfonts[i] instanceof NativeFont)) {
3686 fontMapNames.put(allfonts[i].getFontName(null),
3687 allfonts[i]);
3688 }
3689 }
3690
3691 String[] platformNames = getFontNamesFromPlatform();
3692 if (platformNames != null) {
3693 for (int i=0; i<platformNames.length; i++) {
3694 if (!isNameForRegisteredFile(platformNames[i])) {
3695 fontMapNames.put(platformNames[i], null);
3696 }
3697 }
3698 }
3699
3700 String[] fontNames = null;
3701 if (fontMapNames.size() > 0) {
3702 fontNames = new String[fontMapNames.size()];
3703 Object [] keyNames = fontMapNames.keySet().toArray();
3704 for (int i=0; i < keyNames.length; i++) {
3705 fontNames[i] = (String)keyNames[i];
3706 }
3707 }
3708 Font[] fonts = new Font[fontNames.length];
3709 for (int i=0; i < fontNames.length; i++) {
3710 fonts[i] = new Font(fontNames[i], Font.PLAIN, 1);
3711 Font2D f2d = (Font2D)fontMapNames.get(fontNames[i]);
3712 if (f2d != null) {
3713 FontAccess.getFontAccess().setFont2D(fonts[i], f2d.handle);
3714 }
3715 }
3716 allFonts = fonts;
3717 }
3718
3719 Font []copyFonts = new Font[allFonts.length];
3720 System.arraycopy(allFonts, 0, copyFonts, 0, allFonts.length);
3721 return copyFonts;
3722 }
3723
3724 /**
3725 * Get a list of installed fonts in the requested {@link Locale}.
3726 * The list contains the fonts Family Names.
3727 * If Locale is null, the default locale is used.
3728 *
3729 * @param requestedLocale, if null the default locale is used.
3730 * @return list of installed fonts in the system.
3731 */
3732 public String[] getInstalledFontFamilyNames(Locale requestedLocale) {
3733 if (requestedLocale == null) {
3734 requestedLocale = Locale.getDefault();
3735 }
3736 if (allFamilies != null && lastDefaultLocale != null &&
3737 requestedLocale.equals(lastDefaultLocale)) {
3738 String[] copyFamilies = new String[allFamilies.length];
3739 System.arraycopy(allFamilies, 0, copyFamilies,
3740 0, allFamilies.length);
3741 return copyFamilies;
3742 }
3743
3744 TreeMap<String,String> familyNames = new TreeMap<String,String>();
3745 // these names are always there and aren't localised
3746 String str;
3747 str = Font.SERIF; familyNames.put(str.toLowerCase(), str);
3748 str = Font.SANS_SERIF; familyNames.put(str.toLowerCase(), str);
3749 str = Font.MONOSPACED; familyNames.put(str.toLowerCase(), str);
3750 str = Font.DIALOG; familyNames.put(str.toLowerCase(), str);
3751 str = Font.DIALOG_INPUT; familyNames.put(str.toLowerCase(), str);
3752
3753 /* Platform APIs may be used to get the set of available family
3754 * names for the current default locale so long as it is the same
3755 * as the start-up system locale, rather than loading all fonts.
3756 */
3757 if (requestedLocale.equals(getSystemStartupLocale()) &&
3758 getFamilyNamesFromPlatform(familyNames, requestedLocale)) {
3759 /* Augment platform names with JRE font family names */
3760 getJREFontFamilyNames(familyNames, requestedLocale);
3761 } else {
3762 loadFontFiles();
3763 Font2D[] physicalfonts = getPhysicalFonts();
3764 for (int i=0; i < physicalfonts.length; i++) {
3765 if (!(physicalfonts[i] instanceof NativeFont)) {
3766 String name =
3767 physicalfonts[i].getFamilyName(requestedLocale);
3768 familyNames.put(name.toLowerCase(requestedLocale), name);
3769 }
3770 }
3771 }
3772
3773 String[] retval = new String[familyNames.size()];
3774 Object [] keyNames = familyNames.keySet().toArray();
3775 for (int i=0; i < keyNames.length; i++) {
3776 retval[i] = (String)familyNames.get(keyNames[i]);
3777 }
3778 if (requestedLocale.equals(Locale.getDefault())) {
3779 lastDefaultLocale = requestedLocale;
3780 allFamilies = new String[retval.length];
3781 System.arraycopy(retval, 0, allFamilies, 0, allFamilies.length);
3782 }
3783 return retval;
3784 }
3785
3786 public void register1dot0Fonts() {
3787 java.security.AccessController.doPrivileged(
3788 new java.security.PrivilegedAction() {
3789 public Object run() {
3790 String type1Dir = "/usr/openwin/lib/X11/fonts/Type1";
3791 registerFontsInDir(type1Dir, true, Font2D.TYPE1_RANK,
3792 false, false);
3793 return null;
3794 }
3795 });
3796 }
3797
3798 /* Really we need only the JRE fonts family names, but there's little
3799 * overhead in doing this the easy way by adding all the currently
3800 * known fonts.
3801 */
3802 protected void getJREFontFamilyNames(TreeMap<String,String> familyNames,
3803 Locale requestedLocale) {
3804 registerDeferredJREFonts(jreFontDirName);
3805 Font2D[] physicalfonts = getPhysicalFonts();
3806 for (int i=0; i < physicalfonts.length; i++) {
3807 if (!(physicalfonts[i] instanceof NativeFont)) {
3808 String name =
3809 physicalfonts[i].getFamilyName(requestedLocale);
3810 familyNames.put(name.toLowerCase(requestedLocale), name);
3811 }
3812 }
3813 }
3814
3815 /**
3816 * Default locale can be changed but we need to know the initial locale
3817 * as that is what is used by native code. Changing Java default locale
3818 * doesn't affect that.
3819 * Returns the locale in use when using native code to communicate
3820 * with platform APIs. On windows this is known as the "system" locale,
3821 * and it is usually the same as the platform locale, but not always,
3822 * so this method also checks an implementation property used only
3823 * on windows and uses that if set.
3824 */
3825 private static Locale systemLocale = null;
3826 private static Locale getSystemStartupLocale() {
3827 if (systemLocale == null) {
3828 systemLocale = (Locale)
3829 java.security.AccessController.doPrivileged(
3830 new java.security.PrivilegedAction() {
3831 public Object run() {
3832 /* On windows the system locale may be different than the
3833 * user locale. This is an unsupported configuration, but
3834 * in that case we want to return a dummy locale that will
3835 * never cause a match in the usage of this API. This is
3836 * important because Windows documents that the family
3837 * names of fonts are enumerated using the language of
3838 * the system locale. BY returning a dummy locale in that
3839 * case we do not use the platform API which would not
3840 * return us the names we want.
3841 */
3842 String fileEncoding = System.getProperty("file.encoding", "");
3843 String sysEncoding = System.getProperty("sun.jnu.encoding");
3844 if (sysEncoding != null && !sysEncoding.equals(fileEncoding)) {
3845 return Locale.ROOT;
3846 }
3847
3848 String language = System.getProperty("user.language", "en");
3849 String country = System.getProperty("user.country","");
3850 String variant = System.getProperty("user.variant","");
3851 return new Locale(language, country, variant);
3852 }
3853 });
3854 }
3855 return systemLocale;
3856 }
3857
3858 void addToPool(FileFont font) {
3859
3860 FileFont fontFileToClose = null;
3861 int freeSlot = -1;
3862
3863 synchronized (fontFileCache) {
3864 /* Avoid duplicate entries in the pool, and don't close() it,
3865 * since this method is called only from within open().
3866 * Seeing a duplicate is most likely to happen if the thread
3867 * was interrupted during a read, forcing perhaps repeated
3868 * close and open calls and it eventually it ends up pointing
3869 * at the same slot.
3870 */
3871 for (int i=0;i<CHANNELPOOLSIZE;i++) {
3872 if (fontFileCache[i] == font) {
3873 return;
3874 }
3875 if (fontFileCache[i] == null && freeSlot < 0) {
3876 freeSlot = i;
3877 }
3878 }
3879 if (freeSlot >= 0) {
3880 fontFileCache[freeSlot] = font;
3881 return;
3882 } else {
3883 /* replace with new font. */
3884 fontFileToClose = fontFileCache[lastPoolIndex];
3885 fontFileCache[lastPoolIndex] = font;
3886 /* lastPoolIndex is updated so that the least recently opened
3887 * file will be closed next.
3888 */
3889 lastPoolIndex = (lastPoolIndex+1) % CHANNELPOOLSIZE;
3890 }
3891 }
3892 /* Need to close the font file outside of the synchronized block,
3893 * since its possible some other thread is in an open() call on
3894 * this font file, and could be holding its lock and the pool lock.
3895 * Releasing the pool lock allows that thread to continue, so it can
3896 * then release the lock on this font, allowing the close() call
3897 * below to proceed.
3898 * Also, calling close() is safe because any other thread using
3899 * the font we are closing() synchronizes all reading, so we
3900 * will not close the file while its in use.
3901 */
3902 if (fontFileToClose != null) {
3903 fontFileToClose.close();
3904 }
3905 }
3906
3907 protected FontUIResource getFontConfigFUIR(String family, int style,
3908 int size)
3909 {
3910 return new FontUIResource(family, style, size);
3911 }
3912 }